machineconfig 7.57__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 (104) hide show
  1. machineconfig/cluster/sessions_managers/utils/maker.py +21 -9
  2. machineconfig/jobs/installer/custom/boxes.py +2 -2
  3. machineconfig/jobs/installer/custom/hx.py +3 -3
  4. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  5. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
  6. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -1
  7. machineconfig/jobs/installer/custom_dev/sysabc.py +36 -28
  8. machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
  9. machineconfig/jobs/installer/installer_data.json +127 -25
  10. machineconfig/jobs/installer/package_groups.py +20 -13
  11. machineconfig/profile/create_links_export.py +2 -2
  12. machineconfig/scripts/__init__.py +0 -4
  13. machineconfig/scripts/linux/wrap_mcfg +1 -1
  14. machineconfig/scripts/python/agents.py +22 -17
  15. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +3 -0
  16. machineconfig/scripts/python/croshell.py +22 -17
  17. machineconfig/scripts/python/devops.py +3 -4
  18. machineconfig/scripts/python/devops_navigator.py +0 -4
  19. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  20. machineconfig/scripts/python/fire_jobs.py +19 -18
  21. machineconfig/scripts/python/ftpx.py +36 -12
  22. machineconfig/scripts/python/helpers/ast_search.py +74 -0
  23. machineconfig/scripts/python/helpers/qr_code.py +166 -0
  24. machineconfig/scripts/python/helpers/repo_rag.py +325 -0
  25. machineconfig/scripts/python/helpers/symantic_search.py +25 -0
  26. machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
  27. machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
  28. machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
  29. machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
  30. machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
  31. machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
  32. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +4 -5
  33. machineconfig/scripts/python/helpers_devops/cli_nw.py +88 -7
  34. machineconfig/scripts/python/helpers_devops/cli_self.py +7 -6
  35. machineconfig/scripts/python/helpers_devops/cli_share_file.py +9 -9
  36. machineconfig/scripts/python/helpers_devops/cli_share_server.py +13 -12
  37. machineconfig/scripts/python/helpers_devops/cli_terminal.py +7 -6
  38. machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -73
  39. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  40. machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
  41. machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -3
  42. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +23 -13
  43. machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
  44. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +7 -4
  45. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  46. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  47. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  48. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +5 -5
  49. machineconfig/scripts/python/helpers_utils/download.py +152 -0
  50. machineconfig/scripts/python/helpers_utils/path.py +81 -31
  51. machineconfig/scripts/python/interactive.py +2 -2
  52. machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
  53. machineconfig/scripts/python/msearch.py +21 -2
  54. machineconfig/scripts/python/nw/address.py +132 -0
  55. machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
  56. machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
  57. machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
  58. machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
  59. machineconfig/scripts/python/sessions.py +35 -20
  60. machineconfig/scripts/python/terminal.py +2 -2
  61. machineconfig/scripts/python/utils.py +12 -10
  62. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  63. machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
  64. machineconfig/settings/shells/nushell/config.nu +2 -2
  65. machineconfig/settings/shells/nushell/env.nu +45 -6
  66. machineconfig/settings/shells/nushell/init.nu +282 -95
  67. machineconfig/settings/shells/pwsh/init.ps1 +1 -0
  68. machineconfig/settings/shells/zsh/init.sh +0 -7
  69. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  70. machineconfig/setup_windows/uv.ps1 +8 -1
  71. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
  72. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
  73. machineconfig/utils/accessories.py +7 -4
  74. machineconfig/utils/code.py +6 -4
  75. machineconfig/utils/files/headers.py +2 -2
  76. machineconfig/utils/installer_utils/install_from_url.py +180 -0
  77. machineconfig/utils/installer_utils/installer_class.py +53 -47
  78. machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +71 -65
  79. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
  80. machineconfig/utils/links.py +2 -2
  81. machineconfig/utils/meta.py +30 -16
  82. machineconfig/utils/options.py +4 -4
  83. machineconfig/utils/path_extended.py +3 -3
  84. machineconfig/utils/path_helper.py +33 -31
  85. machineconfig/utils/schemas/layouts/layout_types.py +1 -1
  86. machineconfig/utils/ssh.py +143 -409
  87. machineconfig/utils/ssh_utils/abc.py +8 -0
  88. machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
  89. machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
  90. machineconfig/utils/ssh_utils/utils.py +141 -0
  91. machineconfig/utils/ssh_utils/wsl.py +168 -0
  92. machineconfig/utils/upgrade_packages.py +2 -1
  93. machineconfig/utils/ve.py +11 -4
  94. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/METADATA +1 -1
  95. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/RECORD +102 -92
  96. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/entry_points.txt +2 -2
  97. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  98. machineconfig/scripts/python/explore.py +0 -49
  99. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  100. /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
  101. /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
  102. /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
  103. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/WHEEL +0 -0
  104. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/top_level.txt +0 -0
@@ -188,15 +188,7 @@ class CommandTree(Tree[CommandInfo]):
188
188
  description="Configure your shell profile",
189
189
  command="devops config shell",
190
190
  parent="config",
191
- help_text="devops config shell <copy|reference>"
192
- ))
193
-
194
- config_node.add("🔗 path - Navigate PATH variable", data=CommandInfo(
195
- name="path",
196
- description="Navigate PATH variable with TUI",
197
- command="devops config path",
198
- parent="config",
199
- help_text="devops config path"
191
+ help_text="devops config shell --which <default|nushell>"
200
192
  ))
201
193
 
202
194
  config_node.add("🔗 starship-theme - Select starship theme", data=CommandInfo(
@@ -560,7 +552,23 @@ class CommandTree(Tree[CommandInfo]):
560
552
  description="Choose a process to kill interactively",
561
553
  command="utils kill-process",
562
554
  parent="utils",
563
- help_text="utils kill-process"
555
+ help_text="utils kill-process --interactive"
556
+ ))
557
+
558
+ utils_node.add("📚 path - Navigate PATH variable", data=CommandInfo(
559
+ name="path",
560
+ description="Navigate PATH variable with TUI",
561
+ command="utils path",
562
+ parent="utils",
563
+ help_text="utils path"
564
+ ))
565
+
566
+ utils_node.add("⬆️ upgrade-packages - Upgrade dependencies", data=CommandInfo(
567
+ name="upgrade-packages",
568
+ description="Upgrade project dependencies",
569
+ command="utils upgrade-packages",
570
+ parent="utils",
571
+ help_text="utils upgrade-packages"
564
572
  ))
565
573
 
566
574
  utils_node.add("⬇️ download - Download file", data=CommandInfo(
@@ -571,18 +579,42 @@ class CommandTree(Tree[CommandInfo]):
571
579
  help_text="utils download <url> --destination <path> --decompress"
572
580
  ))
573
581
 
574
- utils_node.add("📄 merge-pdfs - Merge PDF files", data=CommandInfo(
575
- name="merge-pdfs",
576
- description="Merge two PDF files into one",
577
- command="utils merge-pdfs",
578
- parent="utils",
579
- help_text="utils merge-pdfs <file1> <file2> --output <file>"
580
- ))
581
-
582
582
  utils_node.add("🖥️ get-machine-specs - Get machine specifications", data=CommandInfo(
583
583
  name="get-machine-specs",
584
584
  description="Get machine specifications",
585
585
  command="utils get-machine-specs",
586
586
  parent="utils",
587
587
  help_text="utils get-machine-specs"
588
+ ))
589
+
590
+ utils_node.add("🚀 init-project - Initialize project", data=CommandInfo(
591
+ name="init-project",
592
+ description="Initialize a project with a uv virtual environment and install dev packages",
593
+ command="utils init-project",
594
+ parent="utils",
595
+ help_text="utils init-project"
596
+ ))
597
+
598
+ utils_node.add("✏️ edit - Open file in editor", data=CommandInfo(
599
+ name="edit",
600
+ description="Open a file in the default editor",
601
+ command="utils edit",
602
+ parent="utils",
603
+ help_text="utils edit <file>"
604
+ ))
605
+
606
+ utils_node.add("📄 pdf-merge - Merge PDF files", data=CommandInfo(
607
+ name="pdf-merge",
608
+ description="Merge two PDF files into one",
609
+ command="utils pdf-merge",
610
+ parent="utils",
611
+ help_text="utils pdf-merge <file1> <file2> --output <file>"
612
+ ))
613
+
614
+ utils_node.add("� pdf-compress - Compress PDF file", data=CommandInfo(
615
+ name="pdf-compress",
616
+ description="Compress a PDF file",
617
+ command="utils pdf-compress",
618
+ parent="utils",
619
+ help_text="utils pdf-compress <file> --output <file>"
588
620
  ))
@@ -61,8 +61,9 @@ def main(
61
61
  typer.Exit(code=1)
62
62
  return ""
63
63
  repo_local_root = PathExtended(repo_local_obj.working_dir) # cwd might have been in a sub directory of repo_root, so its better to redefine it.
64
+ local_relative_home = PathExtended(repo_local_root.expanduser().absolute().relative_to(Path.home()))
64
65
  PathExtended(CONFIG_ROOT).joinpath("remote").mkdir(parents=True, exist_ok=True)
65
- repo_remote_root = PathExtended(CONFIG_ROOT).joinpath("remote", repo_local_root.rel2home())
66
+ repo_remote_root = PathExtended(CONFIG_ROOT).joinpath("remote", local_relative_home)
66
67
  repo_remote_root.delete(sure=True)
67
68
  try:
68
69
  console.print(Panel("📥 DOWNLOADING REMOTE REPOSITORY", title_align="left", border_style="blue"))
@@ -104,7 +105,7 @@ git pull originEnc master
104
105
  uv_project_dir = f"""{str(Path.home().joinpath("code/machineconfig"))}"""
105
106
  uv_with = None
106
107
  else:
107
- uv_with = ["machineconfig>=7.57"]
108
+ uv_with = ["machineconfig>=7.79"]
108
109
  uv_project_dir = None
109
110
 
110
111
  import tempfile
@@ -135,7 +136,8 @@ git pull originEnc master
135
136
  def func2(remote_repo: str, local_repo: str, cloud: str):
136
137
  from machineconfig.scripts.python.helpers_repos.sync import delete_remote_repo_copy_and_push_local
137
138
  delete_remote_repo_copy_and_push_local(remote_repo=remote_repo, local_repo=local_repo, cloud=cloud)
138
- program_1_py = lambda_to_python_script(lambda: func2(remote_repo=str(repo_remote_root), local_repo=str(repo_local_root), cloud=str(cloud_resolved)), in_global=True, import_module=False)
139
+ program_1_py = lambda_to_python_script(lambda: func2(remote_repo=str(repo_remote_root), local_repo=str(repo_local_root), cloud=str(cloud_resolved)),
140
+ in_global=True, import_module=False)
139
141
  program1, _pyfile1 = get_uv_command_executing_python_script(python_script=program_1_py, uv_with=uv_with, uv_project_dir=uv_project_dir)
140
142
  # ================================================================================
141
143
  option2 = "Delete local repo and replace it with remote copy:"
@@ -160,7 +162,8 @@ sudo chmod +x $HOME/dotfiles/scripts/linux -R
160
162
  inspect_repos(repo_local_root=repo_local_root, repo_remote_root=repo_remote_root)
161
163
  # program_3_py = function_to_script(func=func, call_with_kwargs={"repo_local_root": str(repo_local_root), "repo_remote_root": str(repo_remote_root)})
162
164
  # shell_file_3 = get_shell_file_executing_python_script(python_script=program_3_py, ve_path=None, executable=executable)
163
- program_3_py = lambda_to_python_script(lambda: func(repo_local_root=str(repo_local_root), repo_remote_root=str(repo_remote_root)), in_global=True, import_module=False)
165
+ program_3_py = lambda_to_python_script(lambda: func(repo_local_root=str(repo_local_root), repo_remote_root=str(repo_remote_root)),
166
+ in_global=True, import_module=False)
164
167
  program3, _pyfile3 = get_uv_command_executing_python_script(python_script=program_3_py, uv_with=uv_with, uv_project_dir=uv_project_dir)
165
168
  # ================================================================================
166
169
 
@@ -8,7 +8,7 @@ def analyze_repo_development(repo_path: Annotated[str, typer.Argument(..., help=
8
8
  from pathlib import Path
9
9
  count_lines_path = Path(count_lines.__file__)
10
10
  # --project $HOME/code/ machineconfig --group plot
11
- cmd = f"""uv run --python 3.14 --with "machineconfig[plot]>=7.57" {count_lines_path} analyze-over-time {repo_path}"""
11
+ cmd = f"""uv run --python 3.14 --with "machineconfig[plot]>=7.79" {count_lines_path} analyze-over-time {repo_path}"""
12
12
  from machineconfig.utils.code import run_shell_script
13
13
  run_shell_script(cmd)
14
14
 
@@ -38,7 +38,8 @@ def resolve_spec_path(directory: Optional[str], cloud: Optional[str]) -> Path:
38
38
  repos_root = resolve_directory(directory)
39
39
  from machineconfig.utils.path_extended import PathExtended
40
40
  if not repos_root.exists() or repos_root.name != "repos.json":
41
- candidate = Path(CONFIG_ROOT).joinpath("repos").joinpath(PathExtended(repos_root).rel2home()).joinpath("repos.json")
41
+ relative_repos_root = PathExtended(repos_root).expanduser().absolute().relative_to(Path.home())
42
+ candidate = Path(CONFIG_ROOT).joinpath("repos").joinpath(relative_repos_root).joinpath("repos.json")
42
43
  repos_root = candidate
43
44
  if not repos_root.exists():
44
45
  cloud_name: Optional[str]
@@ -242,7 +242,8 @@ def main_record(repos_root: Path):
242
242
  tree_structure = build_tree_structure(repo_records, repos_root)
243
243
  print(tree_structure)
244
244
 
245
- save_path = CONFIG_ROOT.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
245
+ relative_repos_root = PathExtended(repos_root).expanduser().absolute().relative_to(Path.home())
246
+ save_path = CONFIG_ROOT.joinpath("repos").joinpath(relative_repos_root).joinpath("repos.json")
246
247
  save_json(obj=res, path=save_path, indent=4)
247
248
  pprint(f"📁 Result saved at {PathExtended(save_path)}")
248
249
  print(">>>>>>>>> Finished Recording")
@@ -1,7 +1,6 @@
1
1
 
2
2
 
3
3
  from typing import Optional, Annotated
4
- from pathlib import Path
5
4
  import typer
6
5
 
7
6
 
@@ -13,20 +12,21 @@ def create_from_function(
13
12
  from machineconfig.utils.ve import get_ve_activate_line, get_ve_path_and_ipython_profile
14
13
  from machineconfig.utils.options import choose_from_options
15
14
  from machineconfig.utils.path_helper import match_file_name, sanitize_path
16
- from machineconfig.utils.path_extended import PathExtended
17
15
  from machineconfig.utils.accessories import get_repo_root
16
+ from pathlib import Path
17
+
18
18
 
19
19
  path_obj = sanitize_path(path)
20
20
  if not path_obj.exists():
21
21
  suffixes = {".py"}
22
- choice_file = match_file_name(sub_string=path, search_root=PathExtended.cwd(), suffixes=suffixes)
22
+ choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
23
23
  elif path_obj.is_dir():
24
24
  from machineconfig.utils.path_helper import search_for_files_of_interest
25
25
  print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
26
26
  files = search_for_files_of_interest(path_obj, suffixes={".py", ".sh", ".ps1"})
27
27
  print(f"🔍 Got #{len(files)} results.")
28
28
  choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
29
- choice_file = PathExtended(choice_file)
29
+ choice_file = Path(choice_file)
30
30
  else:
31
31
  choice_file = path_obj
32
32
 
@@ -52,7 +52,7 @@ def create_from_function(
52
52
  from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
53
53
  layout: LayoutConfig = {"layoutName": "fireNprocess", "layoutTabs": []}
54
54
  for an_arg in range(num_process):
55
- layout["layoutTabs"].append({"tabName": f"tab{an_arg}", "startDir": str(PathExtended.cwd()), "command": f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={num_process}"})
55
+ layout["layoutTabs"].append({"tabName": f"tab{an_arg}", "startDir": str(Path.cwd()), "command": f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={num_process}"})
56
56
  print(layout)
57
57
  run_zellij_layout(layout_config=layout)
58
58
 
@@ -0,0 +1,152 @@
1
+
2
+
3
+ from typing import Annotated, Optional
4
+ import typer
5
+ from pathlib import Path
6
+
7
+
8
+ def download(
9
+ url: Annotated[Optional[str], typer.Argument(..., help="The URL to download the file from.")] = None,
10
+ decompress: Annotated[bool, typer.Option(..., "--decompress", "-d", help="Decompress the file if it's an archive.")] = False,
11
+ output: Annotated[Optional[str], typer.Option("--output", "-o", help="The output file path.")] = None,
12
+ output_dir: Annotated[Optional[str], typer.Option("--output-dir", help="Directory to place the downloaded file in.")] = None,
13
+ ) -> Optional["Path"]:
14
+
15
+ import subprocess
16
+ from urllib.parse import parse_qs, unquote, urlparse
17
+ from requests import Response
18
+ import requests
19
+ from pathlib import Path
20
+ if url is None:
21
+ typer.echo("❌ Error: URL is required.", err=True)
22
+ return None
23
+ if output is not None and output_dir is not None:
24
+ typer.echo("❌ Error: --output and --output-dir cannot be used together.", err=True)
25
+ return None
26
+ typer.echo(f"📥 Downloading from: {url}")
27
+
28
+ def _sanitize_candidate_filename(name: str) -> Optional[str]:
29
+ candidate = Path(name).name.strip()
30
+ if not candidate or candidate in {".", ".."}:
31
+ return None
32
+ return candidate
33
+
34
+ def _filename_from_content_disposition(header_value: Optional[str]) -> Optional[str]:
35
+ if header_value is None:
36
+ return None
37
+ parts = [segment.strip() for segment in header_value.split(";")]
38
+ for part in parts:
39
+ lower = part.lower()
40
+ if lower.startswith("filename*="):
41
+ value = part.split("=", 1)[1]
42
+ value = value.strip().strip('"')
43
+ if "''" in value:
44
+ value = value.split("''", 1)[1]
45
+ decoded = unquote(value)
46
+ sanitized = _sanitize_candidate_filename(decoded)
47
+ if sanitized is not None:
48
+ return sanitized
49
+ if lower.startswith("filename="):
50
+ value = part.split("=", 1)[1].strip().strip('"')
51
+ decoded = unquote(value)
52
+ sanitized = _sanitize_candidate_filename(decoded)
53
+ if sanitized is not None:
54
+ return sanitized
55
+ return None
56
+
57
+ def _filename_from_url(source_url: str) -> Optional[str]:
58
+ parsed = urlparse(source_url)
59
+ url_candidate = _sanitize_candidate_filename(unquote(Path(parsed.path).name))
60
+ if url_candidate is not None:
61
+ return url_candidate
62
+ query_params = parse_qs(parsed.query, keep_blank_values=True)
63
+ for key, values in query_params.items():
64
+ lower_key = key.lower()
65
+ if "name" in lower_key or "file" in lower_key:
66
+ for value in values:
67
+ sanitized = _sanitize_candidate_filename(unquote(value))
68
+ if sanitized is not None:
69
+ return sanitized
70
+ return None
71
+
72
+ def _resolve_download_path(request_url: str, response: Response, requested_output: Optional[str], requested_output_dir: Optional[str]) -> Path:
73
+ if requested_output is not None:
74
+ return Path(requested_output)
75
+ header_candidate = _filename_from_content_disposition(response.headers.get("content-disposition"))
76
+ if header_candidate is None:
77
+ header_candidate = _filename_from_url(response.url)
78
+ if header_candidate is None:
79
+ header_candidate = _filename_from_url(request_url)
80
+ if header_candidate is None:
81
+ header_candidate = "downloaded_file"
82
+ if requested_output_dir is not None:
83
+ return Path(requested_output_dir) / header_candidate
84
+ return Path(header_candidate)
85
+
86
+ try:
87
+ with requests.get(url, allow_redirects=True, stream=True, timeout=60) as response:
88
+ response.raise_for_status()
89
+ download_path = _resolve_download_path(url, response, output, output_dir)
90
+ download_path.parent.mkdir(parents=True, exist_ok=True)
91
+ total_size_header = response.headers.get("content-length", "0")
92
+ try:
93
+ total_size = int(total_size_header)
94
+ except (TypeError, ValueError):
95
+ total_size = 0
96
+ if total_size <= 0:
97
+ with open(download_path, "wb") as file_handle:
98
+ file_handle.write(response.content)
99
+ else:
100
+ downloaded = 0
101
+ chunk_size = 8192 * 4
102
+ with open(download_path, "wb") as file_handle:
103
+ for chunk in response.iter_content(chunk_size=chunk_size):
104
+ if not chunk:
105
+ continue
106
+ file_handle.write(chunk)
107
+ downloaded += len(chunk)
108
+ progress = (downloaded / total_size) * 100
109
+ typer.echo(f"\r⏬ Progress: {progress:.1f}% ({downloaded}/{total_size} bytes)", nl=False)
110
+ typer.echo()
111
+ except requests.exceptions.RequestException as exception:
112
+ typer.echo(f"❌ Download failed: {exception}", err=True)
113
+ return None
114
+ except OSError as exception:
115
+ typer.echo(f"❌ File write error: {exception}", err=True)
116
+ return None
117
+
118
+ typer.echo(f"✅ Downloaded to: {download_path}")
119
+ result_path: Path = download_path
120
+
121
+ if decompress:
122
+ typer.echo(f"📦 Decompressing: {download_path}")
123
+ base_name = download_path.stem
124
+ if base_name in {"", ".", ".."}:
125
+ base_name = "extracted"
126
+ extract_dir = download_path.parent / base_name
127
+ extract_dir.mkdir(parents=True, exist_ok=True)
128
+ try:
129
+ subprocess.run(
130
+ ["ouch", "decompress", str(download_path), "--dir", str(extract_dir)],
131
+ check=True,
132
+ capture_output=True,
133
+ text=True,
134
+ )
135
+ typer.echo(f"✅ Decompressed to: {extract_dir}")
136
+ if download_path.exists():
137
+ download_path.unlink()
138
+ typer.echo(f"🗑️ Removed archive: {download_path}")
139
+ result_path = extract_dir
140
+ except subprocess.CalledProcessError as exception:
141
+ typer.echo(f"❌ Decompression failed: {exception.stderr}", err=True)
142
+ return None
143
+ except FileNotFoundError:
144
+ typer.echo("❌ Error: ouch command not found. Please install ouch.", err=True)
145
+ typer.echo("💡 Install with: cargo install ouch", err=True)
146
+ return None
147
+
148
+ return result_path.resolve()
149
+
150
+
151
+ if __name__ == "__main__":
152
+ pass
@@ -3,9 +3,7 @@
3
3
 
4
4
  import typer
5
5
 
6
- from typing import Optional
7
- from pathlib import Path
8
- from typing import Annotated, Literal, TypedDict
6
+ from typing import Optional, Annotated, Literal, TypedDict
9
7
 
10
8
 
11
9
  def path():
@@ -17,43 +15,76 @@ def path():
17
15
  uv_with = ["textual"]
18
16
  uv_project_dir = None
19
17
  if not Path.home().joinpath("code/machineconfig").exists():
20
- uv_with.append("machineconfig>=7.57")
18
+ uv_with.append("machineconfig>=7.79")
21
19
  else:
22
20
  uv_project_dir = str(Path.home().joinpath("code/machineconfig"))
23
21
  run_shell_script(get_uv_command_executing_python_script(python_script=path.read_text(encoding="utf-8"), uv_with=uv_with, uv_project_dir=uv_project_dir)[0])
24
22
 
25
23
 
26
- def init_project(python: Annotated[Literal["3.13", "3.14"], typer.Option("--python", "-p", help="Python version for the uv virtual environment.")]= "3.13") -> None:
27
- _ = python
28
- repo_root = Path.cwd()
29
- if not (repo_root / "pyproject.toml").exists():
30
- typer.echo(" Error: pyproject.toml not found.", err=True)
31
- raise typer.Exit(code=1)
32
- print("Adding group `plot` with common data science and plotting packages...")
33
- script = """
34
- uv add --group plot \
35
- # Data & computation
36
- numpy pandas polars duckdb-engine python-magic \
37
- # Plotting / visualization
38
- matplotlib plotly kaleido \
39
- # Notebooks / interactive
40
- ipython ipykernel jupyterlab nbformat marimo \
41
- # Code analysis / type checking / linting
42
- mypy pyright ruff pylint pyrefly \
43
- # Packaging / build / dev
44
- cleanpy \
45
- # CLI / debugging / utilities
46
- ipdb pudb \
47
- # Type hints for packages
48
- types-python-dateutil types-pyyaml types-requests types-tqdm \
49
- types-mysqlclient types-paramiko types-pytz types-sqlalchemy types-toml types-urllib3 \
50
-
24
+ def init_project(
25
+ name: Annotated[Optional[str], typer.Option("--name", "-n", help="Name of the project.")]= None,
26
+ tmp_directory: Annotated[bool, typer.Option("--tmp-directory/--no-tmp-directory", "-t/-nt", help="Use a temporary directory for the project initialization.")]= False,
27
+ python: Annotated[Literal["3.13", "3.14"], typer.Option("--python", "-p", help="Python version for the uv virtual environment.")]= "3.13",
28
+ packages: Annotated[Optional[str], typer.Option("--packages", "-p", help="Additional packages to include in the uv virtual environment.")]= None,
29
+ group: Annotated[Optional[str], typer.Option("--group", "-g", help="Group name for the packages.")]= "plot",
30
+ types_packages: Annotated[bool, typer.Option("--types-packages/--no-types-packages", "-T/-NT", help="Include types packages for better type hinting.")]= True,
31
+ linting_debug_packages: Annotated[bool, typer.Option("--linting-debug-packages/--no-linting-debug-packages", "-L/-NL", help="Include linting and debugging packages.")]= True,
32
+ ia_packages: Annotated[bool, typer.Option("--ia-packages/--no-ia-packages", "-I/-NI", help="Include interactive and IA packages.")]= True,
33
+ plot_packages: Annotated[bool, typer.Option("--plot-packages/--no-plot-packages", "-P/-NP", help="Include plotting packages.")]= True,
34
+ data_packages: Annotated[bool, typer.Option("--data-packages/--no-data-packages", "-D/-ND", help="Include data manipulation packages.")]= True,
35
+ ) -> None:
36
+ if packages is not None:
37
+ packages_add_line = f"uv add {packages}"
38
+ else:
39
+ packages_add_line = ""
40
+ from pathlib import Path
41
+ if not tmp_directory:
42
+ repo_root = Path.cwd()
43
+ if not (repo_root / "pyproject.toml").exists():
44
+ typer.echo("❌ Error: pyproject.toml not found.", err=True)
45
+ raise typer.Exit(code=1)
46
+ starting_code = ""
47
+ else:
48
+ if name is not None:
49
+ from machineconfig.utils.accessories import randstr
50
+ repo_root = Path.home().joinpath(f"tmp_results/tmp_projects/{name}")
51
+ else:
52
+ from machineconfig.utils.accessories import randstr
53
+ repo_root = Path.home().joinpath(f"tmp_results/tmp_projects/{randstr(6)}")
54
+ repo_root.mkdir(parents=True, exist_ok=True)
55
+ print(f"Using temporary directory for project initialization: {repo_root}")
56
+ starting_code = f"""
57
+ cd {repo_root}
58
+ uv init --python {python}
59
+ uv venv
51
60
  """
52
- from machineconfig.utils.code import run_shell_script
53
- run_shell_script(script)
61
+ print(f"Adding group `{group}` with common data science and plotting packages...")
62
+ total_packages: list[str] = []
63
+
64
+ if types_packages:
65
+ total_packages.append("types-python-dateutil types-pyyaml types-requests types-tqdm types-mysqlclient types-paramiko types-pytz types-sqlalchemy types-toml types-urllib3")
66
+ if linting_debug_packages:
67
+ total_packages.append("mypy pyright ruff pylint pyrefly cleanpy ipdb pudb")
68
+ if ia_packages:
69
+ total_packages.append("ipython ipykernel jupyterlab nbformat marimo")
70
+ if plot_packages:
71
+ total_packages.append("python-magic matplotlib plotly kaleido")
72
+ if data_packages:
73
+ total_packages.append("numpy pandas polars duckdb-engine sqlalchemy psycopg2-binary pyarrow tqdm openpyxl")
74
+ from machineconfig.utils.ve import get_ve_activate_line
75
+ script = f"""
76
+ {starting_code}
77
+ {packages_add_line}
78
+ uv add --group {group} {" ".join(total_packages)}
79
+ {get_ve_activate_line(ve_root=str(repo_root.joinpath(".venv")))}
80
+ ls
81
+ """
82
+ from machineconfig.utils.code import exit_then_run_shell_script
83
+ exit_then_run_shell_script(script)
54
84
 
55
85
 
56
86
  def edit_file_with_hx(path: Annotated[Optional[str], typer.Argument(..., help="The root directory of the project to edit, or a file path.")] = None) -> None:
87
+ from pathlib import Path
57
88
  if path is None:
58
89
  root_path = Path.cwd()
59
90
  print(f"No path provided. Using current working directory: {root_path}")
@@ -82,6 +113,13 @@ class MachineSpecs(TypedDict):
82
113
  system: str
83
114
  distro: str
84
115
  home_dir: str
116
+ hostname: str
117
+ release: str
118
+ version: str
119
+ machine: str
120
+ processor: str
121
+ python_version: str
122
+ user: str
85
123
 
86
124
 
87
125
  def get_machine_specs() -> MachineSpecs:
@@ -90,11 +128,21 @@ def get_machine_specs() -> MachineSpecs:
90
128
  UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
91
129
  command = f"""{UV_RUN_CMD} --with distro python -c "import distro; print(distro.name(pretty=True))" """
92
130
  import subprocess
131
+ from pathlib import Path
132
+ import socket
133
+ import os
93
134
  distro = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
94
135
  specs: MachineSpecs = {
95
136
  "system": platform.system(),
96
137
  "distro": distro,
97
138
  "home_dir": str(Path.home()),
139
+ "hostname": socket.gethostname(),
140
+ "release": platform.release(),
141
+ "version": platform.version(),
142
+ "machine": platform.machine(),
143
+ "processor": platform.processor() or "Unknown",
144
+ "python_version": platform.python_version(),
145
+ "user": os.getenv("USER") or os.getenv("USERNAME") or "Unknown",
98
146
  }
99
147
  print(specs)
100
148
  from machineconfig.utils.source_of_truth import CONFIG_ROOT
@@ -104,3 +152,5 @@ def get_machine_specs() -> MachineSpecs:
104
152
  path.write_text(json.dumps(specs, indent=4), encoding="utf-8")
105
153
  return specs
106
154
 
155
+ if __name__ == "__main__":
156
+ get_machine_specs()
@@ -31,7 +31,7 @@ console = Console()
31
31
 
32
32
 
33
33
  def display_header() -> None:
34
- from machineconfig.utils.installer import get_machineconfig_version
34
+ from machineconfig.utils.installer_utils.installer_runner import get_machineconfig_version
35
35
  from rich.align import Align
36
36
 
37
37
  # Fancy ASCII art header
@@ -111,7 +111,7 @@ def execute_installations(selected_options: list[str]) -> None:
111
111
  console.print(Panel("⚡ [bold bright_yellow]CLI APPLICATIONS[/bold bright_yellow]\n[italic]Command-line tools installation[/italic]", border_style="bright_yellow"))
112
112
  console.print("🔧 Installing CLI applications", style="bold cyan")
113
113
  try:
114
- from machineconfig.utils.installer_utils.installer import main as devops_devapps_install_main
114
+ from machineconfig.utils.installer_utils.installer_cli import main_installer_cli as devops_devapps_install_main
115
115
  devops_devapps_install_main(group=True, which=maybe_a_group, interactive=False)
116
116
  console.print("✅ CLI applications installed successfully", style="bold green")
117
117
  except Exception as e:
@@ -57,3 +57,7 @@ def get_app():
57
57
  def main():
58
58
  app = get_app()
59
59
  app()
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()
@@ -1,8 +1,27 @@
1
1
 
2
2
  import typer
3
+ from typing import Annotated
3
4
 
4
5
 
5
- def machineconfig_find():
6
+ def machineconfig_search(
7
+ directory: Annotated[str, typer.Option(..., "--directory", "-d", help="The directory to search")] = ".",
8
+ ast: Annotated[bool, typer.Option(..., "--ast", "-a", help="The abstract syntax tree search/ tree sitter search of symbols")] = False):
9
+
10
+ if ast:
11
+ from machineconfig.scripts.python.helpers.ast_search import get_repo_symbols
12
+ symbols = get_repo_symbols(directory)
13
+ from machineconfig.utils.options import choose_from_options
14
+ try:
15
+ res = choose_from_options(options=symbols, msg="Select a symbol to search for:", fzf=True, multi=False)
16
+ from rich import print_json
17
+ import json
18
+ res_json = json.dumps(res, indent=4)
19
+ print_json(res_json)
20
+ return None
21
+ except Exception as e:
22
+ print(f"❌ Error during selection: {e}")
23
+ return None
24
+
6
25
  from machineconfig.scripts.python.helpers_msearch import FZFG_LINUX_PATH, FZFG_WINDOWS_PATH
7
26
  import platform
8
27
  if platform.system() == "Linux":
@@ -17,5 +36,5 @@ def machineconfig_find():
17
36
 
18
37
  def main():
19
38
  app = typer.Typer(add_completion=False, no_args_is_help=True)
20
- app.command(name="msearch", help="machineconfig search helper", no_args_is_help=False)(machineconfig_find)
39
+ app.command(name="msearch", help="machineconfig search helper", no_args_is_help=False)(machineconfig_search)
21
40
  app()