machineconfig 7.64__py3-none-any.whl → 7.79__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 (78) hide show
  1. machineconfig/cluster/sessions_managers/utils/maker.py +4 -2
  2. machineconfig/jobs/installer/custom_dev/sysabc.py +26 -0
  3. machineconfig/jobs/installer/installer_data.json +70 -2
  4. machineconfig/profile/create_links_export.py +2 -2
  5. machineconfig/scripts/__init__.py +0 -4
  6. machineconfig/scripts/python/agents.py +22 -17
  7. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +3 -0
  8. machineconfig/scripts/python/croshell.py +22 -17
  9. machineconfig/scripts/python/devops.py +1 -1
  10. machineconfig/scripts/python/devops_navigator.py +0 -4
  11. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  12. machineconfig/scripts/python/fire_jobs.py +13 -13
  13. machineconfig/scripts/python/ftpx.py +36 -12
  14. machineconfig/scripts/python/helpers/ast_search.py +74 -0
  15. machineconfig/scripts/python/helpers/qr_code.py +166 -0
  16. machineconfig/scripts/python/helpers/repo_rag.py +325 -0
  17. machineconfig/scripts/python/helpers/symantic_search.py +25 -0
  18. machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
  19. machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
  20. machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
  21. machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
  22. machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
  23. machineconfig/scripts/python/helpers_devops/cli_nw.py +88 -7
  24. machineconfig/scripts/python/helpers_devops/cli_self.py +7 -6
  25. machineconfig/scripts/python/helpers_devops/cli_share_file.py +7 -7
  26. machineconfig/scripts/python/helpers_devops/cli_share_server.py +12 -11
  27. machineconfig/scripts/python/helpers_devops/cli_terminal.py +6 -5
  28. machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -1
  29. machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
  30. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +20 -9
  31. machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
  32. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +5 -3
  33. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  34. machineconfig/scripts/python/helpers_utils/download.py +4 -3
  35. machineconfig/scripts/python/helpers_utils/path.py +81 -31
  36. machineconfig/scripts/python/interactive.py +1 -1
  37. machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
  38. machineconfig/scripts/python/msearch.py +21 -2
  39. machineconfig/scripts/python/nw/address.py +132 -0
  40. machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
  41. machineconfig/scripts/python/terminal.py +2 -2
  42. machineconfig/scripts/python/utils.py +10 -9
  43. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  44. machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
  45. machineconfig/settings/shells/nushell/config.nu +2 -2
  46. machineconfig/settings/shells/nushell/env.nu +45 -6
  47. machineconfig/settings/shells/nushell/init.nu +282 -95
  48. machineconfig/settings/shells/pwsh/init.ps1 +1 -0
  49. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  50. machineconfig/setup_windows/uv.ps1 +8 -1
  51. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
  52. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
  53. machineconfig/utils/accessories.py +7 -4
  54. machineconfig/utils/code.py +4 -2
  55. machineconfig/utils/installer_utils/install_from_url.py +180 -0
  56. machineconfig/utils/installer_utils/installer_class.py +18 -10
  57. machineconfig/utils/installer_utils/installer_cli.py +14 -9
  58. machineconfig/utils/links.py +2 -2
  59. machineconfig/utils/meta.py +2 -2
  60. machineconfig/utils/options.py +3 -3
  61. machineconfig/utils/path_extended.py +1 -1
  62. machineconfig/utils/path_helper.py +0 -1
  63. machineconfig/utils/ssh.py +143 -409
  64. machineconfig/utils/ssh_utils/abc.py +8 -0
  65. machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
  66. machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
  67. machineconfig/utils/ssh_utils/utils.py +141 -0
  68. machineconfig/utils/ssh_utils/wsl.py +168 -0
  69. machineconfig/utils/upgrade_packages.py +2 -1
  70. machineconfig/utils/ve.py +11 -4
  71. {machineconfig-7.64.dist-info → machineconfig-7.79.dist-info}/METADATA +1 -1
  72. {machineconfig-7.64.dist-info → machineconfig-7.79.dist-info}/RECORD +77 -68
  73. {machineconfig-7.64.dist-info → machineconfig-7.79.dist-info}/entry_points.txt +2 -2
  74. machineconfig/scripts/python/explore.py +0 -49
  75. /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
  76. /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
  77. {machineconfig-7.64.dist-info → machineconfig-7.79.dist-info}/WHEEL +0 -0
  78. {machineconfig-7.64.dist-info → machineconfig-7.79.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,180 @@
1
+
2
+
3
+ import platform
4
+ from urllib.parse import urlparse
5
+
6
+ import typer
7
+ from typing import Optional, TypeAlias, cast
8
+
9
+ from machineconfig.utils.installer_utils.installer_class import install_deb_package
10
+ from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows
11
+ from machineconfig.utils.path_extended import DECOMPRESS_SUPPORTED_FORMATS, PathExtended
12
+ from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
13
+
14
+ SUPPORTED_GITHUB_HOSTS = {"github.com", "www.github.com"}
15
+
16
+ GitHubAsset: TypeAlias = dict[str, object]
17
+ GitHubRelease: TypeAlias = dict[str, object]
18
+
19
+
20
+ def _extract_repo_name(github_url: str) -> str:
21
+ parsed = urlparse(github_url)
22
+ parts = [part for part in parsed.path.strip("/").split("/") if part]
23
+ if len(parts) < 2:
24
+ return ""
25
+ owner, repo = parts[0], parts[1]
26
+ if repo == "":
27
+ return ""
28
+ return f"{owner}/{repo}"
29
+
30
+
31
+ def _fetch_latest_release(repo_name: str) -> Optional[GitHubRelease]:
32
+ import json
33
+ import requests
34
+ try:
35
+ response = requests.get(f"https://api.github.com/repos/{repo_name}/releases/latest", timeout=30)
36
+ except requests.RequestException:
37
+ return None
38
+ if response.status_code != 200:
39
+ return None
40
+ try:
41
+ data = response.json()
42
+ except json.JSONDecodeError:
43
+ return None
44
+ if not isinstance(data, dict):
45
+ return None
46
+ return cast(GitHubRelease, data)
47
+
48
+
49
+ def _format_size(size_bytes: int) -> str:
50
+ if size_bytes <= 0:
51
+ return "0 B"
52
+ units = ("B", "KiB", "MiB", "GiB", "TiB")
53
+ value = float(size_bytes)
54
+ index = 0
55
+ while value >= 1024 and index < len(units) - 1:
56
+ value /= 1024
57
+ index += 1
58
+ return f"{value:.1f} {units[index]}"
59
+
60
+
61
+ def _derive_tool_name(repo_name: str, asset_name: str) -> str:
62
+ repo_segment = repo_name.split("/", maxsplit=1)[-1]
63
+ repo_clean = repo_segment.replace(".git", "").lower()
64
+ repo_filtered = "".join(char for char in repo_clean if char.isalnum())
65
+ if repo_filtered:
66
+ return repo_filtered
67
+ asset_clean = asset_name.lower()
68
+ asset_filtered = "".join(char for char in asset_clean if char.isalnum())
69
+ if asset_filtered:
70
+ return asset_filtered
71
+ return "githubapp"
72
+
73
+
74
+ def install_from_github_url(github_url: str) -> None:
75
+ from machineconfig.utils.options import choose_from_options
76
+ from rich.console import Console
77
+ from rich.panel import Panel
78
+
79
+ console = Console()
80
+ repo_name = _extract_repo_name(github_url)
81
+ if repo_name == "":
82
+ console.print(Panel(f"Invalid GitHub URL: {github_url}", title="❌ Error", border_style="red"))
83
+ raise typer.Exit(1)
84
+ console.print(Panel(f"Fetching latest release for [green]{repo_name}[/green]", title="🌐 GitHub", border_style="blue"))
85
+ release_raw = _fetch_latest_release(repo_name)
86
+ if not release_raw:
87
+ console.print(Panel("No releases available for this repository.", title="❌ Error", border_style="red"))
88
+ raise typer.Exit(1)
89
+ release = release_raw
90
+ assets_value = release.get("assets", [])
91
+ assets: list[GitHubAsset] = []
92
+ if isinstance(assets_value, list):
93
+ for asset in assets_value:
94
+ if isinstance(asset, dict):
95
+ typed_asset: GitHubAsset = {}
96
+ name_value = asset.get("name")
97
+ url_value = asset.get("browser_download_url")
98
+ size_value = asset.get("size")
99
+ content_value = asset.get("content_type")
100
+ if isinstance(name_value, str):
101
+ typed_asset["name"] = name_value
102
+ if isinstance(url_value, str):
103
+ typed_asset["browser_download_url"] = url_value
104
+ if isinstance(size_value, int):
105
+ typed_asset["size"] = size_value
106
+ if isinstance(content_value, str):
107
+ typed_asset["content_type"] = content_value
108
+ assets.append(typed_asset)
109
+ if not assets:
110
+ console.print(Panel("No downloadable assets found in the latest release.", title="❌ Error", border_style="red"))
111
+ raise typer.Exit(1)
112
+ binary_assets = assets
113
+ selection_pool = binary_assets if binary_assets else assets
114
+ if not selection_pool:
115
+ console.print(Panel("No assets available for installation.", title="❌ Error", border_style="red"))
116
+ raise typer.Exit(1)
117
+ options_map: dict[str, GitHubAsset] = {}
118
+ for asset in selection_pool:
119
+ name = asset.get("name")
120
+ download_url = asset.get("browser_download_url")
121
+ if not isinstance(name, str) or not isinstance(download_url, str) or name == "" or download_url == "":
122
+ continue
123
+ size_value = asset.get("size")
124
+ size = size_value if isinstance(size_value, int) else 0
125
+ label = f"{name} [{_format_size(size)}]"
126
+ options_map[label] = asset
127
+ if not options_map:
128
+ console.print(Panel("Release assets lack download URLs.", title="❌ Error", border_style="red"))
129
+ raise typer.Exit(1)
130
+ selection_label = choose_from_options(options=list(options_map.keys()), msg="Select a release asset", multi=False, header="📦 GitHub Release Assets", fzf=True)
131
+ selected_asset = options_map[selection_label]
132
+ download_url_value = selected_asset.get("browser_download_url")
133
+ asset_name_value = selected_asset.get("name")
134
+ if not isinstance(download_url_value, str) or download_url_value == "":
135
+ console.print(Panel("Selected asset lacks a download URL.", title="❌ Error", border_style="red"))
136
+ raise typer.Exit(1)
137
+ asset_name = asset_name_value if isinstance(asset_name_value, str) else "github_binary"
138
+ version_value = release.get("tag_name")
139
+ version = version_value if isinstance(version_value, str) and version_value != "" else "latest"
140
+ console.print(Panel(f"Downloading [cyan]{asset_name}[/cyan]", title="⬇️ Download", border_style="magenta"))
141
+ archive_path = PathExtended(download_url_value).download(folder=INSTALL_TMP_DIR)
142
+ extracted_path = archive_path
143
+ if extracted_path.suffix in DECOMPRESS_SUPPORTED_FORMATS:
144
+ extracted_path = archive_path.decompress()
145
+ archive_path.delete(sure=True)
146
+ if extracted_path.is_dir():
147
+ nested_items = list(extracted_path.glob("*"))
148
+ if len(nested_items) == 1:
149
+ nested_path = PathExtended(nested_items[0])
150
+ if nested_path.suffix in DECOMPRESS_SUPPORTED_FORMATS:
151
+ extracted_path = nested_path.decompress()
152
+ nested_path.delete(sure=True)
153
+ if extracted_path.suffix == ".deb":
154
+ install_deb_package(extracted_path)
155
+ tool_name_deb = _derive_tool_name(repo_name, asset_name)
156
+ INSTALL_VERSION_ROOT.joinpath(tool_name_deb).parent.mkdir(parents=True, exist_ok=True)
157
+ INSTALL_VERSION_ROOT.joinpath(tool_name_deb).write_text(version, encoding="utf-8")
158
+ console.print(Panel(f"Installed Debian package for [green]{tool_name_deb}[/green]", title="✅ Complete", border_style="green"))
159
+ return
160
+ system_name = platform.system()
161
+ tool_name = _derive_tool_name(repo_name, asset_name)
162
+ rename_target = f"{tool_name}.exe" if system_name == "Windows" else tool_name
163
+ try:
164
+ if system_name == "Windows":
165
+ installed_path = find_move_delete_windows(downloaded_file_path=extracted_path, exe_name=tool_name, delete=True, rename_to=rename_target)
166
+ elif system_name in {"Linux", "Darwin"}:
167
+ installed_path = find_move_delete_linux(downloaded=extracted_path, tool_name=tool_name, delete=True, rename_to=rename_target)
168
+ else:
169
+ console.print(Panel(f"Unsupported operating system: {system_name}", title="❌ Error", border_style="red"))
170
+ raise typer.Exit(1)
171
+ except IndexError:
172
+ if system_name == "Windows":
173
+ installed_path = find_move_delete_windows(downloaded_file_path=extracted_path, exe_name=None, delete=True, rename_to=rename_target)
174
+ elif system_name in {"Linux", "Darwin"}:
175
+ installed_path = find_move_delete_linux(downloaded=extracted_path, tool_name="", delete=True, rename_to=rename_target)
176
+ else:
177
+ raise
178
+ INSTALL_VERSION_ROOT.joinpath(tool_name).parent.mkdir(parents=True, exist_ok=True)
179
+ INSTALL_VERSION_ROOT.joinpath(tool_name).write_text(version, encoding="utf-8")
180
+ console.print(Panel(f"Installed [green]{tool_name}[/green] to {installed_path}\nVersion: {version}", title="✅ Complete", border_style="green"))
@@ -12,18 +12,22 @@ from urllib.parse import urlparse
12
12
 
13
13
 
14
14
  def install_deb_package(downloaded: PathExtended) -> None:
15
+ from rich import print as rprint
16
+ from rich.panel import Panel
15
17
  print(f"📦 Installing .deb package: {downloaded}")
16
18
  assert platform.system() == "Linux"
17
19
  result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
18
20
  success = result.returncode == 0 and result.stderr == ""
19
21
  if not success:
22
+ from rich.console import Group
20
23
  desc = "Installing .deb"
21
- print(f"❌ {desc} failed")
24
+ sub_panels = []
22
25
  if result.stdout:
23
- print(f"STDOUT: {result.stdout}")
26
+ sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
24
27
  if result.stderr:
25
- print(f"STDERR: {result.stderr}")
26
- print(f"Return code: {result.returncode}")
28
+ sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
29
+ group_content = Group(f" {desc} failed\nReturn code: {result.returncode}", *sub_panels)
30
+ rprint(Panel(group_content, title=desc, style="red"))
27
31
  print("🗑️ Cleaning up .deb package...")
28
32
  downloaded.delete(sure=True)
29
33
 
@@ -79,19 +83,23 @@ class Installer:
79
83
  version_to_be_installed: str = "unknown" # Initialize to ensure it's always bound
80
84
  if repo_url == "CMD":
81
85
  if any(pm in installer_arch_os for pm in ["npm ", "pip ", "winget ", "brew ", "curl "]):
86
+ from rich import print as rprint
87
+ from rich.panel import Panel
88
+ from rich.console import Group
82
89
  package_manager = installer_arch_os.split(" ", maxsplit=1)[0]
83
90
  print(f"📦 Using package manager: {installer_arch_os}")
84
91
  desc = package_manager + " installation"
85
92
  version_to_be_installed = package_manager + "Latest"
86
- result = subprocess.run(installer_arch_os, shell=True, capture_output=True, text=False)
87
- success = result.returncode == 0 and result.stderr == "".encode()
93
+ result = subprocess.run(installer_arch_os, shell=True, capture_output=False, text=True)
94
+ success = result.returncode == 0 and result.stderr == ""
88
95
  if not success:
89
- print(f"❌ {desc} failed")
96
+ sub_panels = []
90
97
  if result.stdout:
91
- print(f"STDOUT: {result.stdout}")
98
+ sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
92
99
  if result.stderr:
93
- print(f"STDERR: {result.stderr}")
94
- print(f"Return code: {result.returncode}")
100
+ sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
101
+ group_content = Group(f" {desc} failed\nReturn code: {result.returncode}", *sub_panels)
102
+ rprint(Panel(group_content, title=desc, style="red"))
95
103
  elif installer_arch_os.endswith((".sh", ".py", ".ps1")):
96
104
  import machineconfig.jobs.installer as module
97
105
  from pathlib import Path
@@ -1,11 +1,13 @@
1
1
  """Devops Devapps Install"""
2
2
 
3
3
  import typer
4
- from typing import Optional, Annotated
4
+ from typing import Annotated, Optional
5
5
  from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
6
+ from machineconfig.utils.installer_utils.installer_class import Installer
6
7
 
7
8
 
8
- def main(
9
+
10
+ def main_installer_cli(
9
11
  which: Annotated[Optional[str], typer.Argument(..., help="Comma-separated list of program/groups names to install (if --group flag is set).")] = None,
10
12
  group: Annotated[bool, typer.Option(..., "--group", "-g", help="Treat 'which' as a group name. A group is bundle of apps.")] = False,
11
13
  interactive: Annotated[bool, typer.Option(..., "--interactive", "-i", help="Interactive selection of programs to install.")] = False,
@@ -142,17 +144,21 @@ def install_clis(clis_names: list[str]):
142
144
  from machineconfig.utils.installer_utils.installer_runner import get_installers
143
145
  from machineconfig.utils.installer_utils.installer_class import Installer
144
146
  from rich.console import Console
147
+ all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
145
148
  total_messages: list[str] = []
146
- for a_which in clis_names:
147
- all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
149
+ for a_cli_name in clis_names:
150
+ if "github.com" in a_cli_name.lower():
151
+ from machineconfig.utils.installer_utils.install_from_url import install_from_github_url
152
+ install_from_github_url(github_url=a_cli_name)
153
+ continue
148
154
  selected_installer = None
149
155
  for installer in all_installers:
150
156
  app_name = installer["appName"]
151
- if app_name.lower() == a_which.lower():
157
+ if app_name.lower() == a_cli_name.lower():
152
158
  selected_installer = installer
153
159
  break
154
160
  if selected_installer is None:
155
- _handle_installer_not_found(a_which, all_names=[inst["appName"] for inst in all_installers])
161
+ _handle_installer_not_found(a_cli_name, all_names=[inst["appName"] for inst in all_installers])
156
162
  return None
157
163
  message = Installer(selected_installer).install_robust(version=None) # finish the task
158
164
  total_messages.append(message)
@@ -169,13 +175,12 @@ def install_if_missing(which: str):
169
175
  print(f"✅ {which} is already installed.")
170
176
  return
171
177
  print(f"⏳ {which} not found. Installing...")
172
- from machineconfig.utils.installer_utils.installer_cli import main
173
- main(which=which, interactive=False)
178
+ from machineconfig.utils.installer_utils.installer_cli import main_installer_cli
179
+ main_installer_cli(which=which, interactive=False)
174
180
 
175
181
 
176
182
  if __name__ == "__main__":
177
183
  from machineconfig.utils.schemas.installer.installer_types import InstallerData
178
184
  from machineconfig.utils.installer_utils.installer_class import Installer
179
-
180
185
  _ = InstallerData, Installer
181
186
  pass
@@ -164,7 +164,7 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
164
164
  else:
165
165
  # Files are different, use on_conflict strategy
166
166
  import subprocess
167
- command = f"""delta --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
167
+ command = f"""delta --paging never --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
168
168
  try:
169
169
  console.print(Panel(f"🆘 CONFLICT DETECTED | Showing diff between {config_file_default_path} and {self_managed_config_file_path}", title="Conflict Detected", expand=False))
170
170
  subprocess.run(command, shell=True, check=True)
@@ -293,7 +293,7 @@ def copy_map(config_file_default_path: PathExtended, self_managed_config_file_pa
293
293
  else:
294
294
  # Files are different, use on_conflict strategy
295
295
  import subprocess
296
- command = f"""delta --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
296
+ command = f"""delta --paging never --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
297
297
  try:
298
298
  console.print(Panel(f"🆘 CONFLICT DETECTED | Showing diff between {config_file_default_path} and {self_managed_config_file_path}", title="Conflict Detected", expand=False))
299
299
  subprocess.run(command, shell=True, check=True)
@@ -29,7 +29,8 @@ except (ImportError, ModuleNotFoundError) as ex:
29
29
  return txt
30
30
 
31
31
 
32
- def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_module: bool) -> str:
32
+ def lambda_to_python_script(lmb: Callable[[], Any],
33
+ in_global: bool, import_module: bool) -> str:
33
34
  """
34
35
  caveats: always use keyword arguments in the lambda call for best results.
35
36
  return statement not allowed in the wrapped function (otherwise it can be put in the global space)
@@ -250,7 +251,6 @@ if __name__ == "__main__":
250
251
  import_code_robust = "<import_code_robust>"
251
252
  res = lambda_to_python_script(
252
253
  lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
253
- # in_global=True, import_module=False
254
254
  in_global=True, import_module=False
255
255
  )
256
256
  print(res)
@@ -14,10 +14,10 @@ from typing import Optional, Union, Iterable, overload, Literal, cast
14
14
 
15
15
 
16
16
  @overload
17
- def choose_from_options[T](msg: str, options: Iterable[T], multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
17
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
18
18
  @overload
19
- def choose_from_options[T](msg: str, options: Iterable[T], multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> list[T]: ...
20
- def choose_from_options[T](msg: str, options: Iterable[T], multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> Union[T, list[T]]:
19
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> list[T]: ...
20
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> Union[T, list[T]]:
21
21
  # TODO: replace with https://github.com/tmbo/questionary
22
22
  # # also see https://github.com/charmbracelet/gum
23
23
  options_strings: list[str] = [str(x) for x in options]
@@ -711,7 +711,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
711
711
  :return: pathlib.Path pointing to the destination directory where contents were extracted
712
712
  :raises: FileNotFoundError if archive does not exist; py7zr.Bad7zFile or other error if extraction fails
713
713
  """
714
- import py7zr
714
+ import py7zr # type: ignore
715
715
  import tempfile
716
716
  from pathlib import Path
717
717
  archive_path_obj = Path(archive_path)
@@ -1,4 +1,3 @@
1
- # from machineconfig.utils.path_extended import Path
2
1
  from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
3
2
  from rich.console import Console
4
3
  from rich.panel import Panel