machineconfig 3.7__py3-none-any.whl → 7.69__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 (465) hide show
  1. machineconfig/__init__.py +0 -28
  2. machineconfig/cluster/remote/distribute.py +0 -1
  3. machineconfig/cluster/remote/file_manager.py +0 -2
  4. machineconfig/cluster/remote/script_execution.py +1 -2
  5. machineconfig/cluster/sessions_managers/{enhanced_command_runner.py → helpers/enhanced_command_runner.py} +4 -6
  6. machineconfig/cluster/sessions_managers/helpers/load_balancer_helper.py +145 -0
  7. machineconfig/cluster/sessions_managers/utils/load_balancer.py +53 -0
  8. machineconfig/cluster/sessions_managers/utils/maker.py +69 -0
  9. machineconfig/cluster/sessions_managers/wt_local.py +128 -330
  10. machineconfig/cluster/sessions_managers/wt_local_manager.py +53 -187
  11. machineconfig/cluster/sessions_managers/wt_remote.py +51 -43
  12. machineconfig/cluster/sessions_managers/wt_remote_manager.py +49 -197
  13. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +6 -19
  14. machineconfig/cluster/sessions_managers/wt_utils/manager_persistence.py +52 -0
  15. machineconfig/cluster/sessions_managers/wt_utils/monitoring_helpers.py +50 -0
  16. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +4 -2
  17. machineconfig/cluster/sessions_managers/wt_utils/status_reporting.py +76 -0
  18. machineconfig/cluster/sessions_managers/wt_utils/wt_helpers.py +199 -0
  19. machineconfig/cluster/sessions_managers/zellij_local.py +81 -375
  20. machineconfig/cluster/sessions_managers/zellij_local_manager.py +22 -172
  21. machineconfig/cluster/sessions_managers/zellij_remote.py +40 -41
  22. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -10
  23. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -8
  24. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +5 -20
  25. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +3 -9
  26. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -1
  27. machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_helper.py +298 -0
  28. machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_helper_restart.py +77 -0
  29. machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_helper_with_panes.py +228 -0
  30. machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_manager_helper.py +165 -0
  31. machineconfig/jobs/{python → installer}/check_installations.py +7 -21
  32. machineconfig/jobs/installer/custom/boxes.py +61 -0
  33. machineconfig/jobs/installer/custom/gh.py +128 -0
  34. machineconfig/jobs/{python_custom_installers → installer/custom}/hx.py +84 -18
  35. machineconfig/jobs/installer/custom_dev/alacritty.py +86 -0
  36. machineconfig/jobs/installer/custom_dev/brave.py +82 -0
  37. machineconfig/jobs/installer/custom_dev/bypass_paywall.py +59 -0
  38. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  39. machineconfig/jobs/installer/custom_dev/code.py +63 -0
  40. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/cursor.py +7 -7
  41. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +30 -0
  42. machineconfig/jobs/installer/custom_dev/espanso.py +117 -0
  43. machineconfig/jobs/installer/custom_dev/goes.py +68 -0
  44. machineconfig/jobs/installer/custom_dev/lvim.py +89 -0
  45. machineconfig/jobs/installer/custom_dev/nerdfont.py +111 -0
  46. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +149 -0
  47. machineconfig/jobs/installer/custom_dev/redis.py +88 -0
  48. machineconfig/jobs/installer/custom_dev/sysabc.py +145 -0
  49. machineconfig/jobs/installer/custom_dev/wezterm.py +92 -0
  50. machineconfig/jobs/{python_custom_installers/dev → installer/custom_dev}/winget.py +2 -3
  51. machineconfig/jobs/installer/installer_data.json +3440 -0
  52. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/brave.sh +4 -14
  53. machineconfig/jobs/{python_custom_installers/scripts/linux/warp-cli.sh → installer/linux_scripts/cloudflare_warp_cli.sh} +5 -17
  54. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/docker.sh +6 -18
  55. machineconfig/jobs/installer/linux_scripts/docker_start.sh +37 -0
  56. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/edge.sh +3 -11
  57. machineconfig/jobs/{linux/msc → installer/linux_scripts}/lid.sh +2 -8
  58. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/nerdfont.sh +5 -17
  59. machineconfig/jobs/{linux/msc → installer/linux_scripts}/network.sh +2 -8
  60. machineconfig/jobs/installer/linux_scripts/ngrok.sh +6 -0
  61. machineconfig/jobs/installer/linux_scripts/q.sh +9 -0
  62. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/redis.sh +6 -17
  63. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/vscode.sh +5 -17
  64. machineconfig/jobs/{python_custom_installers/scripts/linux → installer/linux_scripts}/wezterm.sh +4 -12
  65. machineconfig/jobs/installer/package_groups.py +255 -0
  66. machineconfig/logger.py +0 -1
  67. machineconfig/profile/backup.toml +49 -0
  68. machineconfig/profile/bash_shell_profiles.md +11 -0
  69. machineconfig/profile/create_helper.py +74 -0
  70. machineconfig/profile/create_links.py +288 -0
  71. machineconfig/profile/create_links_export.py +100 -0
  72. machineconfig/profile/create_shell_profile.py +136 -0
  73. machineconfig/profile/mapper.toml +258 -0
  74. machineconfig/scripts/__init__.py +0 -4
  75. machineconfig/scripts/linux/{share_cloud.sh → other/share_cloud.sh} +14 -25
  76. machineconfig/scripts/linux/wrap_mcfg +47 -0
  77. machineconfig/scripts/nu/wrap_mcfg.nu +69 -0
  78. machineconfig/scripts/python/agents.py +198 -0
  79. machineconfig/scripts/python/ai/command_runner/command_runner.sh +9 -0
  80. machineconfig/scripts/python/ai/command_runner/prompt.txt +9 -0
  81. machineconfig/scripts/python/ai/generate_files.py +307 -42
  82. machineconfig/scripts/python/ai/{mcinit.py → initai.py} +3 -38
  83. machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +114 -0
  84. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +88 -22
  85. machineconfig/scripts/python/ai/solutions/_shared.py +9 -1
  86. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +4 -1
  87. machineconfig/scripts/python/ai/solutions/copilot/prompts/pyright_fix.md +16 -0
  88. machineconfig/scripts/python/ai/solutions/gemini/settings.json +1 -1
  89. machineconfig/scripts/python/ai/solutions/generic.py +27 -4
  90. machineconfig/scripts/python/ai/vscode_tasks.py +37 -0
  91. machineconfig/scripts/python/cloud.py +29 -0
  92. machineconfig/scripts/python/croshell.py +129 -198
  93. machineconfig/scripts/python/define.py +31 -0
  94. machineconfig/scripts/python/devops.py +45 -131
  95. machineconfig/scripts/python/devops_navigator.py +6 -0
  96. machineconfig/scripts/python/env_manager/__init__.py +1 -0
  97. machineconfig/scripts/python/env_manager/path_manager_backend.py +47 -0
  98. machineconfig/scripts/python/env_manager/path_manager_tui.py +228 -0
  99. machineconfig/scripts/python/fire_jobs.py +166 -235
  100. machineconfig/scripts/python/ftpx.py +164 -100
  101. machineconfig/scripts/python/helpers/ast_search.py +74 -0
  102. machineconfig/scripts/python/helpers/repo_rag.py +325 -0
  103. machineconfig/scripts/python/helpers/symantic_search.py +25 -0
  104. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.json +14 -0
  105. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.py +37 -0
  106. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_cursor_agents.py +22 -0
  107. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_gemini.py +42 -0
  108. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +30 -0
  109. machineconfig/scripts/python/helpers_agents/fire_agents_help_launch.py +110 -0
  110. machineconfig/scripts/python/helpers_agents/fire_agents_helper_types.py +34 -0
  111. machineconfig/scripts/python/helpers_agents/fire_agents_load_balancer.py +22 -0
  112. machineconfig/scripts/python/helpers_agents/templates/prompt.txt +6 -0
  113. machineconfig/scripts/python/helpers_agents/templates/template.ps1 +14 -0
  114. machineconfig/scripts/python/helpers_agents/templates/template.sh +24 -0
  115. machineconfig/scripts/python/{cloud_copy.py → helpers_cloud/cloud_copy.py} +52 -39
  116. machineconfig/scripts/python/{cloud_mount.py → helpers_cloud/cloud_mount.py} +13 -18
  117. machineconfig/scripts/python/helpers_cloud/cloud_sync.py +81 -0
  118. machineconfig/scripts/python/{helpers → helpers_cloud}/helpers2.py +3 -3
  119. machineconfig/scripts/python/helpers_croshell/crosh.py +39 -0
  120. machineconfig/scripts/python/{scheduler.py → helpers_croshell/scheduler.py} +0 -1
  121. machineconfig/scripts/python/{start_slidev.py → helpers_croshell/start_slidev.py} +32 -20
  122. machineconfig/scripts/python/helpers_devops/cli_config.py +95 -0
  123. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +89 -0
  124. machineconfig/scripts/python/helpers_devops/cli_data.py +25 -0
  125. machineconfig/scripts/python/helpers_devops/cli_nw.py +134 -0
  126. machineconfig/scripts/python/helpers_devops/cli_repos.py +182 -0
  127. machineconfig/scripts/python/helpers_devops/cli_self.py +134 -0
  128. machineconfig/scripts/python/helpers_devops/cli_share_file.py +137 -0
  129. machineconfig/scripts/python/helpers_devops/cli_share_server.py +141 -0
  130. machineconfig/scripts/python/helpers_devops/cli_terminal.py +156 -0
  131. machineconfig/scripts/python/helpers_devops/cli_utils.py +96 -0
  132. machineconfig/scripts/python/{devops_backup_retrieve.py → helpers_devops/devops_backup_retrieve.py} +7 -10
  133. machineconfig/scripts/python/helpers_devops/devops_status.py +511 -0
  134. machineconfig/scripts/python/helpers_devops/devops_update_repos.py +269 -0
  135. machineconfig/scripts/python/helpers_devops/themes/choose_pwsh_theme.ps1 +81 -0
  136. machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +3 -0
  137. machineconfig/scripts/python/{choose_wezterm_theme.py → helpers_devops/themes/choose_wezterm_theme.py} +2 -2
  138. machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -2
  139. machineconfig/scripts/python/{helpers/helpers4.py → helpers_fire_command/file_wrangler.py} +57 -89
  140. machineconfig/scripts/python/helpers_fire_command/fire_jobs_args_helper.py +145 -0
  141. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +110 -0
  142. machineconfig/scripts/python/helpers_msearch/__init__.py +5 -0
  143. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfag +1 -1
  144. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfg +1 -1
  145. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfrga +1 -1
  146. machineconfig/scripts/python/helpers_navigator/__init__.py +20 -0
  147. machineconfig/scripts/python/helpers_navigator/command_builder.py +111 -0
  148. machineconfig/scripts/python/helpers_navigator/command_detail.py +44 -0
  149. machineconfig/scripts/python/helpers_navigator/command_tree.py +620 -0
  150. machineconfig/scripts/python/helpers_navigator/data_models.py +28 -0
  151. machineconfig/scripts/python/helpers_navigator/main_app.py +272 -0
  152. machineconfig/scripts/python/helpers_navigator/search_bar.py +15 -0
  153. machineconfig/scripts/python/helpers_repos/action.py +209 -0
  154. machineconfig/scripts/python/helpers_repos/action_helper.py +150 -0
  155. machineconfig/scripts/python/{repos_helper_clone.py → helpers_repos/clone.py} +6 -7
  156. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +218 -0
  157. machineconfig/scripts/python/helpers_repos/count_lines.py +348 -0
  158. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +17 -0
  159. machineconfig/scripts/python/helpers_repos/entrypoint.py +77 -0
  160. machineconfig/scripts/python/helpers_repos/grource.py +340 -0
  161. machineconfig/scripts/python/{repos_helper_record.py → helpers_repos/record.py} +7 -4
  162. machineconfig/scripts/python/helpers_repos/sync.py +66 -0
  163. machineconfig/scripts/python/{repos_helper_update.py → helpers_repos/update.py} +3 -3
  164. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +58 -0
  165. machineconfig/scripts/python/helpers_utils/download.py +152 -0
  166. machineconfig/scripts/python/helpers_utils/path.py +108 -0
  167. machineconfig/scripts/python/interactive.py +187 -0
  168. machineconfig/scripts/python/mcfg_entry.py +63 -0
  169. machineconfig/scripts/python/msearch.py +40 -0
  170. machineconfig/scripts/python/{devops_add_identity.py → nw/devops_add_identity.py} +1 -3
  171. machineconfig/scripts/python/{devops_add_ssh_key.py → nw/devops_add_ssh_key.py} +74 -44
  172. machineconfig/scripts/{linux → python/nw}/mount_nfs +1 -1
  173. machineconfig/scripts/python/{mount_nfs.py → nw/mount_nfs.py} +19 -16
  174. machineconfig/scripts/{linux → python/nw}/mount_nw_drive +1 -2
  175. machineconfig/scripts/python/{mount_ssh.py → nw/mount_ssh.py} +7 -8
  176. machineconfig/scripts/python/{onetimeshare.py → nw/onetimeshare.py} +0 -1
  177. machineconfig/scripts/python/nw/ssh_debug_linux.py +391 -0
  178. machineconfig/scripts/python/nw/ssh_debug_windows.py +338 -0
  179. machineconfig/scripts/python/{wifi_conn.py → nw/wifi_conn.py} +1 -51
  180. machineconfig/scripts/python/nw/wsl_windows_transfer.py +67 -0
  181. machineconfig/scripts/python/sessions.py +167 -0
  182. machineconfig/scripts/python/terminal.py +127 -0
  183. machineconfig/scripts/python/utils.py +66 -0
  184. machineconfig/scripts/windows/mounts/Restore-ThunderbirdProfile.ps1 +92 -0
  185. machineconfig/scripts/windows/{mount_nfs.ps1 → mounts/mount_nfs.ps1} +1 -3
  186. machineconfig/scripts/windows/{mount_ssh.ps1 → mounts/mount_ssh.ps1} +1 -1
  187. machineconfig/scripts/windows/{share_smb.ps1 → mounts/share_smb.ps1} +0 -6
  188. machineconfig/scripts/windows/wrap_mcfg.ps1 +60 -0
  189. machineconfig/settings/broot/br.sh +0 -4
  190. machineconfig/settings/broot/conf.toml +1 -1
  191. machineconfig/settings/helix/config.toml +16 -0
  192. machineconfig/settings/helix/languages.toml +13 -4
  193. machineconfig/settings/helix/yazi-picker.sh +12 -0
  194. machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
  195. machineconfig/settings/lf/linux/exe/previewer.sh +9 -3
  196. machineconfig/settings/lf/linux/lfrc +10 -12
  197. machineconfig/settings/lf/windows/fzf_edit.ps1 +2 -2
  198. machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
  199. machineconfig/settings/lf/windows/lfrc +18 -38
  200. machineconfig/settings/lf/windows/mkfile.ps1 +1 -1
  201. machineconfig/settings/linters/.ruff.toml +1 -1
  202. machineconfig/settings/lvim/windows/archive/config_additional.lua +0 -6
  203. machineconfig/settings/marimo/marimo.toml +80 -0
  204. machineconfig/settings/marimo/snippets/globalize.py +34 -0
  205. machineconfig/settings/pistol/pistol.conf +1 -1
  206. machineconfig/settings/shells/bash/init.sh +55 -31
  207. machineconfig/settings/shells/nushell/config.nu +1 -34
  208. machineconfig/settings/shells/nushell/init.nu +127 -0
  209. machineconfig/settings/shells/pwsh/init.ps1 +61 -43
  210. machineconfig/settings/shells/starship/starship.toml +16 -0
  211. machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
  212. machineconfig/settings/shells/wt/settings.json +32 -17
  213. machineconfig/settings/shells/zsh/init.sh +89 -0
  214. machineconfig/settings/svim/linux/init.toml +0 -4
  215. machineconfig/settings/svim/windows/init.toml +0 -3
  216. machineconfig/settings/yazi/init.lua +57 -0
  217. machineconfig/settings/yazi/keymap_linux.toml +79 -0
  218. machineconfig/settings/yazi/keymap_windows.toml +78 -0
  219. machineconfig/settings/yazi/shell/yazi_cd.ps1 +33 -0
  220. machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
  221. machineconfig/settings/yazi/yazi.toml +14 -1
  222. machineconfig/setup_linux/__init__.py +10 -0
  223. machineconfig/setup_linux/apps_desktop.sh +89 -0
  224. machineconfig/setup_linux/apps_gui.sh +64 -0
  225. machineconfig/setup_linux/{nix → others}/cli_installation.sh +9 -29
  226. machineconfig/setup_linux/ssh/openssh_all.sh +25 -0
  227. machineconfig/setup_linux/ssh/openssh_wsl.sh +38 -0
  228. machineconfig/setup_linux/uv.sh +15 -0
  229. machineconfig/setup_linux/web_shortcuts/interactive.sh +28 -203
  230. machineconfig/setup_mac/__init__.py +16 -0
  231. machineconfig/setup_mac/apps_gui.sh +248 -0
  232. machineconfig/setup_mac/ssh/openssh_setup.sh +114 -0
  233. machineconfig/setup_mac/uv.sh +36 -0
  234. machineconfig/setup_windows/__init__.py +8 -0
  235. machineconfig/setup_windows/others/power_options.ps1 +7 -0
  236. machineconfig/setup_windows/ssh/add-sshkey.ps1 +29 -0
  237. machineconfig/setup_windows/ssh/add_identity.ps1 +11 -0
  238. machineconfig/setup_windows/ssh/openssh-server.ps1 +37 -0
  239. machineconfig/setup_windows/uv.ps1 +17 -0
  240. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +28 -189
  241. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +17 -0
  242. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +37 -23
  243. machineconfig/utils/accessories.py +52 -12
  244. machineconfig/utils/cloud/onedrive/README.md +139 -0
  245. machineconfig/utils/code.py +140 -93
  246. machineconfig/utils/files/art/fat_croco.txt +10 -0
  247. machineconfig/utils/files/art/halfwit_croco.txt +9 -0
  248. machineconfig/utils/files/art/happy_croco.txt +22 -0
  249. machineconfig/utils/files/art/water_croco.txt +11 -0
  250. machineconfig/utils/files/ascii_art.py +118 -0
  251. machineconfig/utils/files/dbms.py +257 -0
  252. machineconfig/utils/files/headers.py +68 -0
  253. machineconfig/utils/files/ouch/decompress.py +45 -0
  254. machineconfig/utils/files/read.py +95 -0
  255. machineconfig/utils/installer_utils/github_release_bulk.py +188 -0
  256. machineconfig/utils/installer_utils/install_from_url.py +180 -0
  257. machineconfig/utils/installer_utils/installer_class.py +239 -316
  258. machineconfig/utils/installer_utils/installer_cli.py +186 -0
  259. machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +90 -5
  260. machineconfig/utils/installer_utils/installer_runner.py +191 -0
  261. machineconfig/utils/io.py +77 -24
  262. machineconfig/utils/links.py +309 -100
  263. machineconfig/utils/meta.py +255 -0
  264. machineconfig/utils/notifications.py +1 -1
  265. machineconfig/utils/options.py +19 -47
  266. machineconfig/utils/path_extended.py +111 -121
  267. machineconfig/utils/path_helper.py +75 -22
  268. machineconfig/utils/procs.py +50 -74
  269. machineconfig/utils/scheduler.py +94 -97
  270. machineconfig/utils/scheduling.py +0 -3
  271. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +5 -17
  272. machineconfig/utils/schemas/installer/installer_types.py +28 -6
  273. machineconfig/utils/schemas/layouts/layout_types.py +34 -1
  274. machineconfig/utils/source_of_truth.py +3 -6
  275. machineconfig/utils/ssh.py +742 -254
  276. machineconfig/utils/ssh_utils/utils.py +0 -0
  277. machineconfig/utils/terminal.py +3 -140
  278. machineconfig/utils/tst.py +20 -0
  279. machineconfig/utils/upgrade_packages.py +109 -28
  280. machineconfig/utils/ve.py +13 -5
  281. machineconfig-7.69.dist-info/METADATA +124 -0
  282. machineconfig-7.69.dist-info/RECORD +454 -0
  283. machineconfig-7.69.dist-info/entry_points.txt +15 -0
  284. machineconfig/cluster/templates/cli_click.py +0 -102
  285. machineconfig/cluster/templates/cli_gooey.py +0 -115
  286. machineconfig/cluster/templates/utils.py +0 -51
  287. machineconfig/jobs/linux/msc/cli_agents.sh +0 -32
  288. machineconfig/jobs/python/create_bootable_media.py +0 -16
  289. machineconfig/jobs/python/python_cargo_build_share.py +0 -59
  290. machineconfig/jobs/python/python_ve_symlink.py +0 -29
  291. machineconfig/jobs/python/tasks.py +0 -3
  292. machineconfig/jobs/python/vscode/api.py +0 -48
  293. machineconfig/jobs/python/vscode/link_ve.py +0 -63
  294. machineconfig/jobs/python/vscode/select_interpreter.py +0 -87
  295. machineconfig/jobs/python/vscode/sync_code.py +0 -58
  296. machineconfig/jobs/python_custom_installers/archive/ngrok.py +0 -63
  297. machineconfig/jobs/python_custom_installers/dev/aider.py +0 -37
  298. machineconfig/jobs/python_custom_installers/dev/alacritty.py +0 -65
  299. machineconfig/jobs/python_custom_installers/dev/brave.py +0 -71
  300. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +0 -50
  301. machineconfig/jobs/python_custom_installers/dev/code.py +0 -51
  302. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +0 -78
  303. machineconfig/jobs/python_custom_installers/dev/espanso.py +0 -90
  304. machineconfig/jobs/python_custom_installers/dev/goes.py +0 -55
  305. machineconfig/jobs/python_custom_installers/dev/lvim.py +0 -77
  306. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +0 -68
  307. machineconfig/jobs/python_custom_installers/dev/redis.py +0 -65
  308. machineconfig/jobs/python_custom_installers/dev/reverse_proxy.md +0 -31
  309. machineconfig/jobs/python_custom_installers/dev/wezterm.py +0 -70
  310. machineconfig/jobs/python_custom_installers/docker.py +0 -74
  311. machineconfig/jobs/python_custom_installers/gh.py +0 -97
  312. machineconfig/jobs/python_custom_installers/scripts/linux/docker_start.sh +0 -45
  313. machineconfig/jobs/python_custom_installers/scripts/linux/pgsql.sh +0 -49
  314. machineconfig/jobs/python_custom_installers/scripts/linux/timescaledb.sh +0 -85
  315. machineconfig/jobs/python_custom_installers/warp-cli.py +0 -71
  316. machineconfig/jobs/python_generic_installers/config.json +0 -603
  317. machineconfig/jobs/python_generic_installers/config.json.bak +0 -414
  318. machineconfig/jobs/python_generic_installers/dev/config.archive.json +0 -18
  319. machineconfig/jobs/python_generic_installers/dev/config.json +0 -825
  320. machineconfig/jobs/python_generic_installers/dev/config.json.bak +0 -565
  321. machineconfig/jobs/python_linux_installers/archive/config.json +0 -18
  322. machineconfig/jobs/python_linux_installers/archive/config.json.bak +0 -10
  323. machineconfig/jobs/python_linux_installers/config.json +0 -145
  324. machineconfig/jobs/python_linux_installers/config.json.bak +0 -110
  325. machineconfig/jobs/python_linux_installers/dev/config.json +0 -276
  326. machineconfig/jobs/python_linux_installers/dev/config.json.bak +0 -206
  327. machineconfig/jobs/python_windows_installers/archive/file.json +0 -11
  328. machineconfig/jobs/python_windows_installers/config.json +0 -82
  329. machineconfig/jobs/python_windows_installers/config.json.bak +0 -56
  330. machineconfig/jobs/python_windows_installers/dev/config.json +0 -4
  331. machineconfig/jobs/python_windows_installers/dev/config.json.bak +0 -3
  332. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +0 -14
  333. machineconfig/jobs/windows/start_terminal.ps1 +0 -6
  334. machineconfig/jobs/windows/startup_file.cmd +0 -2
  335. machineconfig/profile/create.py +0 -169
  336. machineconfig/profile/shell.py +0 -176
  337. machineconfig/scripts/cloud/init.sh +0 -119
  338. machineconfig/scripts/linux/choose_wezterm_theme +0 -3
  339. machineconfig/scripts/linux/cloud_copy +0 -2
  340. machineconfig/scripts/linux/cloud_mount +0 -2
  341. machineconfig/scripts/linux/cloud_repo_sync +0 -2
  342. machineconfig/scripts/linux/cloud_sync +0 -2
  343. machineconfig/scripts/linux/croshell +0 -3
  344. machineconfig/scripts/linux/devops +0 -2
  345. machineconfig/scripts/linux/fire +0 -2
  346. machineconfig/scripts/linux/fire_agents +0 -2
  347. machineconfig/scripts/linux/ftpx +0 -2
  348. machineconfig/scripts/linux/fzf2g +0 -21
  349. machineconfig/scripts/linux/fzffg +0 -25
  350. machineconfig/scripts/linux/gh_models +0 -2
  351. machineconfig/scripts/linux/kill_process +0 -2
  352. machineconfig/scripts/linux/mcinit +0 -2
  353. machineconfig/scripts/linux/programs +0 -21
  354. machineconfig/scripts/linux/repos +0 -2
  355. machineconfig/scripts/linux/scheduler +0 -2
  356. machineconfig/scripts/linux/share_smb +0 -1
  357. machineconfig/scripts/linux/start_slidev +0 -2
  358. machineconfig/scripts/linux/start_terminals +0 -3
  359. machineconfig/scripts/linux/warp-cli.sh +0 -122
  360. machineconfig/scripts/linux/wifi_conn +0 -2
  361. machineconfig/scripts/linux/z_ls +0 -104
  362. machineconfig/scripts/python/ai/solutions/copilot/prompts/allLintersAndTypeCheckers.prompt.md +0 -5
  363. machineconfig/scripts/python/archive/im2text.py +0 -34
  364. machineconfig/scripts/python/archive/tmate_conn.py +0 -41
  365. machineconfig/scripts/python/archive/tmate_start.py +0 -44
  366. machineconfig/scripts/python/cloud_repo_sync.py +0 -192
  367. machineconfig/scripts/python/cloud_sync.py +0 -85
  368. machineconfig/scripts/python/devops_devapps_install.py +0 -202
  369. machineconfig/scripts/python/devops_update_repos.py +0 -180
  370. machineconfig/scripts/python/dotfile.py +0 -52
  371. machineconfig/scripts/python/fire_agents.py +0 -176
  372. machineconfig/scripts/python/fire_agents_help_launch.py +0 -143
  373. machineconfig/scripts/python/fire_agents_load_balancer.py +0 -50
  374. machineconfig/scripts/python/fire_jobs_args_helper.py +0 -84
  375. machineconfig/scripts/python/fire_jobs_layout_helper.py +0 -66
  376. machineconfig/scripts/python/get_zellij_cmd.py +0 -15
  377. machineconfig/scripts/python/gh_models.py +0 -104
  378. machineconfig/scripts/python/helpers/repo_sync_helpers.py +0 -114
  379. machineconfig/scripts/python/repos.py +0 -160
  380. machineconfig/scripts/python/snapshot.py +0 -25
  381. machineconfig/scripts/python/start_terminals.py +0 -121
  382. machineconfig/scripts/python/wsl_windows_transfer.py +0 -72
  383. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +0 -1
  384. machineconfig/scripts/windows/cloud_copy.ps1 +0 -1
  385. machineconfig/scripts/windows/cloud_mount.ps1 +0 -1
  386. machineconfig/scripts/windows/cloud_repo_sync.ps1 +0 -1
  387. machineconfig/scripts/windows/cloud_sync.ps1 +0 -1
  388. machineconfig/scripts/windows/croshell.ps1 +0 -1
  389. machineconfig/scripts/windows/devops.ps1 +0 -1
  390. machineconfig/scripts/windows/dotfile.ps1 +0 -1
  391. machineconfig/scripts/windows/fire.ps1 +0 -1
  392. machineconfig/scripts/windows/ftpx.ps1 +0 -1
  393. machineconfig/scripts/windows/gpt.ps1 +0 -1
  394. machineconfig/scripts/windows/grep.ps1 +0 -2
  395. machineconfig/scripts/windows/kill_process.ps1 +0 -1
  396. machineconfig/scripts/windows/mcinit.ps1 +0 -1
  397. machineconfig/scripts/windows/nano.ps1 +0 -3
  398. machineconfig/scripts/windows/pomodoro.ps1 +0 -1
  399. machineconfig/scripts/windows/reload_path.ps1 +0 -3
  400. machineconfig/scripts/windows/repos.ps1 +0 -1
  401. machineconfig/scripts/windows/scheduler.ps1 +0 -1
  402. machineconfig/scripts/windows/snapshot.ps1 +0 -1
  403. machineconfig/scripts/windows/start_slidev.ps1 +0 -1
  404. machineconfig/scripts/windows/start_terminals.ps1 +0 -1
  405. machineconfig/scripts/windows/wifi_conn.ps1 +0 -2
  406. machineconfig/scripts/windows/wsl_rdp_windows_port_forwarding.ps1 +0 -46
  407. machineconfig/scripts/windows/wsl_ssh_windows_port_forwarding.ps1 +0 -76
  408. machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
  409. machineconfig/setup_linux/others/openssh-server_add_pub_key.sh +0 -57
  410. machineconfig/setup_linux/web_shortcuts/ascii_art.sh +0 -93
  411. machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -11
  412. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -52
  413. machineconfig/setup_windows/web_shortcuts/all.ps1 +0 -18
  414. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +0 -36
  415. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +0 -16
  416. machineconfig/setup_windows/web_shortcuts/ssh.ps1 +0 -11
  417. machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +0 -100
  418. machineconfig/utils/ai/generate_file_checklist.py +0 -68
  419. machineconfig/utils/installer.py +0 -255
  420. machineconfig-3.7.dist-info/METADATA +0 -165
  421. machineconfig-3.7.dist-info/RECORD +0 -432
  422. machineconfig-3.7.dist-info/entry_points.txt +0 -18
  423. machineconfig/cluster/{templates → remote}/run_cloud.py +0 -0
  424. machineconfig/cluster/{templates → remote}/run_cluster.py +0 -0
  425. machineconfig/cluster/{templates → remote}/run_remote.py +0 -0
  426. machineconfig/jobs/{python → installer}/__init__.py +0 -0
  427. machineconfig/jobs/{python_custom_installers → installer/custom_dev}/__init__.py +0 -0
  428. machineconfig/{setup_windows/wt_and_pwsh → jobs/installer/powershell_scripts}/install_fonts.ps1 +0 -0
  429. machineconfig/scripts/linux/{share_nfs → other/share_nfs} +0 -0
  430. machineconfig/scripts/linux/{start_docker → other/start_docker} +0 -0
  431. machineconfig/scripts/linux/{switch_ip → other/switch_ip} +0 -0
  432. machineconfig/{jobs/python_generic_installers → scripts/python/helpers_agents}/__init__.py +0 -0
  433. machineconfig/{jobs/python_linux_installers → scripts/python/helpers_agents/agentic_frameworks}/__init__.py +0 -0
  434. machineconfig/scripts/python/{fire_agents_help_search.py → helpers_agents/fire_agents_help_search.py} +0 -0
  435. machineconfig/{jobs/python_linux_installers/dev → scripts/python/helpers_cloud}/__init__.py +0 -0
  436. machineconfig/scripts/python/{helpers → helpers_cloud}/cloud_helpers.py +1 -1
  437. /machineconfig/scripts/python/{helpers → helpers_cloud}/helpers5.py +0 -0
  438. /machineconfig/{jobs/python_windows_installers → scripts/python/helpers_croshell}/__init__.py +0 -0
  439. /machineconfig/scripts/python/{pomodoro.py → helpers_croshell/pomodoro.py} +0 -0
  440. /machineconfig/scripts/python/{viewer.py → helpers_croshell/viewer.py} +0 -0
  441. /machineconfig/scripts/python/{viewer_template.py → helpers_croshell/viewer_template.py} +0 -0
  442. /machineconfig/{jobs/python_windows_installers/archive → scripts/python/helpers_devops}/__init__.py +0 -0
  443. /machineconfig/{jobs/python_windows_installers/dev → scripts/python/helpers_devops/themes}/__init__.py +0 -0
  444. /machineconfig/{jobs/windows/msc/cli_agents.bat → scripts/python/helpers_devops/themes/choose_starship_theme.ps1} +0 -0
  445. /machineconfig/scripts/python/{helpers → helpers_fire_command}/__init__.py +0 -0
  446. /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → helpers_fire_command/fire_jobs_streamlit_helper.py} +0 -0
  447. /machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/skrg +0 -0
  448. /machineconfig/scripts/{windows → python/helpers_msearch/scripts_windows}/fzfb.ps1 +0 -0
  449. /machineconfig/scripts/{windows → python/helpers_msearch/scripts_windows}/fzfg.ps1 +0 -0
  450. /machineconfig/scripts/{windows → python/helpers_msearch/scripts_windows}/fzfrga.bat +0 -0
  451. /machineconfig/{jobs/windows/msc/cli_agents.ps1 → scripts/python/helpers_sessions/__init__.py} +0 -0
  452. /machineconfig/scripts/{windows/share_nfs.ps1 → python/nw/__init__.py} +0 -0
  453. /machineconfig/scripts/{linux → python/nw}/mount_drive +0 -0
  454. /machineconfig/scripts/python/{mount_nw_drive.py → nw/mount_nw_drive.py} +0 -0
  455. /machineconfig/scripts/{linux → python/nw}/mount_smb +0 -0
  456. /machineconfig/scripts/windows/{mount_nw.ps1 → mounts/mount_nw.ps1} +0 -0
  457. /machineconfig/scripts/windows/{mount_smb.ps1 → mounts/mount_smb.ps1} +0 -0
  458. /machineconfig/scripts/windows/{share_cloud.cmd → mounts/share_cloud.cmd} +0 -0
  459. /machineconfig/scripts/windows/{unlock_bitlocker.ps1 → mounts/unlock_bitlocker.ps1} +0 -0
  460. /machineconfig/setup_linux/{web_shortcuts → others}/android.sh +0 -0
  461. /machineconfig/{jobs/windows/archive → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
  462. /machineconfig/{jobs/windows/archive → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
  463. /machineconfig/{settings/yazi/keymap.toml → utils/files/ouch/__init__.py} +0 -0
  464. {machineconfig-3.7.dist-info → machineconfig-7.69.dist-info}/WHEEL +0 -0
  465. {machineconfig-3.7.dist-info → machineconfig-7.69.dist-info}/top_level.txt +0 -0
@@ -1,93 +1,60 @@
1
1
  from machineconfig.utils.accessories import randstr
2
+ from machineconfig.utils.io import decrypt, encrypt
2
3
 
3
4
  from datetime import datetime
4
5
  import time
5
6
  from pathlib import Path
6
7
  import sys
7
8
  import subprocess
9
+ import os
8
10
  from platform import system
9
11
  from typing import Any, Optional, Union, Callable, TypeAlias, Literal
10
12
 
11
13
 
14
+
12
15
  OPLike: TypeAlias = Union[str, "PathExtended", Path, None]
13
16
  PLike: TypeAlias = Union[str, "PathExtended", Path]
14
17
  FILE_MODE: TypeAlias = Literal["r", "w", "x", "a"]
15
18
  SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
19
+ DECOMPRESS_SUPPORTED_FORMATS = [".tar.gz", ".tgz", ".tar", ".gz", ".tar.bz", ".tbz", ".tar.xz", ".zip", ".7z"]
16
20
 
17
21
 
18
- def pwd2key(password: str, salt: Optional[bytes] = None, iterations: int = 10) -> bytes: # Derive a secret key from a given password and salt"""
19
- import base64
20
-
21
- if salt is None:
22
- import hashlib
23
-
24
- m = hashlib.sha256()
25
- m.update(password.encode(encoding="utf-8"))
26
- return base64.urlsafe_b64encode(s=m.digest()) # make url-safe bytes required by Ferent.
27
- from cryptography.hazmat.primitives import hashes
28
- from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
29
-
30
- return base64.urlsafe_b64encode(PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=iterations, backend=None).derive(password.encode()))
31
-
32
-
33
- def encrypt(msg: bytes, key: Optional[bytes] = None, pwd: Optional[str] = None, salted: bool = True, iteration: Optional[int] = None, gen_key: bool = False) -> bytes:
34
- import base64
35
- from cryptography.fernet import Fernet
36
-
37
- salt, iteration = None, None
38
- if pwd is not None: # generate it from password
39
- assert (key is None) and (type(pwd) is str), "❌ You can either pass key or pwd, or none of them, but not both."
40
- import secrets
41
-
42
- iteration = iteration or secrets.randbelow(exclusive_upper_bound=1_000_000)
43
- salt = secrets.token_bytes(nbytes=16) if salted else None
44
- key_resolved = pwd2key(password=pwd, salt=salt, iterations=iteration)
45
- elif key is None:
46
- if gen_key:
47
- key_resolved = Fernet.generate_key()
48
- Path.home().joinpath("dotfiles/creds/data/encrypted_files_key.bytes").write_bytes(key_resolved)
49
- else:
50
- try:
51
- key_resolved = Path.home().joinpath("dotfiles/creds/data/encrypted_files_key.bytes").read_bytes()
52
- print(f"⚠️ Using key from: {Path.home().joinpath('dotfiles/creds/data/encrypted_files_key.bytes')}")
53
- except FileNotFoundError as err:
54
- print("\n" * 3, "~" * 50, """Consider Loading up your dotfiles or pass `gen_key=True` to make and save one.""", "~" * 50, "\n" * 3)
55
- raise FileNotFoundError(err) from err
56
- elif isinstance(key, (str, PathExtended, Path)):
57
- key_resolved = Path(key).read_bytes() # a path to a key file was passed, read it:
58
- elif type(key) is bytes:
59
- key_resolved = key # key passed explicitly
60
- else:
61
- raise TypeError("❌ Key must be either a path, bytes object or None.")
62
- code = Fernet(key=key_resolved).encrypt(msg)
63
- if pwd is not None and salt is not None and iteration is not None:
64
- return base64.urlsafe_b64encode(b"%b%b%b" % (salt, iteration.to_bytes(4, "big"), base64.urlsafe_b64decode(code)))
65
- return code
66
-
67
-
68
- def decrypt(token: bytes, key: Optional[bytes] = None, pwd: Optional[str] = None, salted: bool = True) -> bytes:
69
- import base64
70
-
71
- if pwd is not None:
72
- assert key is None, "❌ You can either pass key or pwd, or none of them, but not both."
73
- if salted:
74
- decoded = base64.urlsafe_b64decode(token)
75
- salt, iterations, token = decoded[:16], decoded[16:20], base64.urlsafe_b64encode(decoded[20:])
76
- key_resolved = pwd2key(password=pwd, salt=salt, iterations=int.from_bytes(bytes=iterations, byteorder="big"))
77
- else:
78
- key_resolved = pwd2key(password=pwd) # trailing `;` prevents IPython from caching the result.
79
- elif type(key) is bytes:
80
- assert pwd is None, "❌ You can either pass key or pwd, or none of them, but not both."
81
- key_resolved = key # passsed explicitly
82
- elif key is None:
83
- key_resolved = Path.home().joinpath("dotfiles/creds/data/encrypted_files_key.bytes").read_bytes() # read from file
84
- elif isinstance(key, (str, Path)):
85
- key_resolved = Path(key).read_bytes() # passed a path to a file containing kwy
86
- else:
87
- raise TypeError(f"❌ Key must be either str, P, Path, bytes or None. Recieved: {type(key)}")
88
- from cryptography.fernet import Fernet
89
-
90
- return Fernet(key=key_resolved).decrypt(token)
22
+ def _is_user_admin() -> bool:
23
+ if os.name == "nt":
24
+ try:
25
+ return __import__("ctypes").windll.shell32.IsUserAnAdmin()
26
+ except Exception: # noqa: BLE001
27
+ import traceback
28
+
29
+ traceback.print_exc()
30
+ print("Admin check failed, assuming not an admin.")
31
+ return False
32
+ return os.getuid() == 0
33
+
34
+
35
+ def _run_shell_command(
36
+ command: str,
37
+ shell_name: str,
38
+ *,
39
+ stdout: Optional[int] = subprocess.PIPE,
40
+ stderr: Optional[int] = subprocess.PIPE,
41
+ stdin: Optional[int] = None,
42
+ check: bool = False,
43
+ ) -> subprocess.CompletedProcess[str]:
44
+ if shell_name in {"powershell", "pwsh"} and sys.platform == "win32":
45
+ args: list[str] = [shell_name, "-Command", command]
46
+ return subprocess.run(args, check=check, text=True, stdout=stdout, stderr=stderr, stdin=stdin)
47
+ executable = "/bin/bash" if shell_name == "bash" and sys.platform != "win32" else None
48
+ return subprocess.run(
49
+ command,
50
+ check=check,
51
+ text=True,
52
+ stdout=stdout,
53
+ stderr=stderr,
54
+ stdin=stdin,
55
+ shell=True,
56
+ executable=executable,
57
+ )
91
58
 
92
59
 
93
60
  def validate_name(astring: str, replace: str = "_") -> str:
@@ -128,7 +95,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
128
95
  print(f"🗑️ ❌ DELETED {repr(self)}.")
129
96
  return self
130
97
 
131
- def move(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, rel2it: bool = False, overwrite: bool = False, verbose: bool = True, parents: bool = True, content: bool = False) -> "PathExtended":
98
+ def move(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, rel2it: bool = False, overwrite: bool = False, verbose: bool = True, parents: bool = True, content: bool = False) -> "PathExtended": # type: ignore
132
99
  path = self._resolve_path(folder=folder, name=name, path=path, default_name=self.absolute().name, rel2it=rel2it)
133
100
  if parents:
134
101
  path.parent.mkdir(parents=True, exist_ok=True)
@@ -154,9 +121,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
154
121
  print(f"🚚 MOVED {repr(self)} ==> {repr(path)}`")
155
122
  return path
156
123
 
157
- def copy(
158
- self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, content: bool = False, verbose: bool = True, append: Optional[str] = None, overwrite: bool = False, orig: bool = False
159
- ) -> "PathExtended": # tested %100 # TODO: replace `content` flag with ability to interpret "*" in resolve method.
124
+ def copy(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, content: bool = False, verbose: bool = True, append: Optional[str] = None, overwrite: bool = False, orig: bool = False) -> "PathExtended": # type: ignore
160
125
  dest = self._resolve_path(folder=folder, name=name, path=path, default_name=self.name, rel2it=False)
161
126
  dest = dest.expanduser().resolve()
162
127
  dest.parent.mkdir(parents=True, exist_ok=True)
@@ -188,7 +153,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
188
153
  # ======================================= File Editing / Reading ===================================
189
154
  def download(self, folder: OPLike = None, name: Optional[str] = None, allow_redirects: bool = True, timeout: Optional[int] = None, params: Any = None) -> "PathExtended":
190
155
  import requests
191
-
192
156
  response = requests.get(self.as_url_str(), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
193
157
  assert response.status_code == 200, f"Download failed with status code {response.status_code}\n{response.text}"
194
158
  if name is not None:
@@ -392,9 +356,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
392
356
  assert target_obj.exists(), f"Target path `{target}` (aka `{target_obj}`) doesn't exist. This will create a broken link."
393
357
  if overwrite and (self.is_symlink() or self.exists()):
394
358
  self.delete(sure=True, verbose=verbose)
395
- from machineconfig.utils.terminal import Terminal
396
-
397
- if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
359
+ if system() == "Windows" and not _is_user_admin(): # you cannot create symlink without priviliages.
398
360
  import win32com.shell.shell # type: ignore # pylint: disable=E0401
399
361
 
400
362
  _proce_info = win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile=sys.executable, lpParameters=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"")
@@ -518,9 +480,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
518
480
  **kwargs: Any,
519
481
  ) -> "PathExtended":
520
482
  path_resolved, slf = self._resolve_path(folder, name, path, self.name).expanduser().resolve(), self.expanduser().resolve()
521
- # if use_7z: # benefits over regular zip and encrypt: can handle very large files with low memory footprint
522
- # path_resolved = path_resolved + '.7z' if not path_resolved.suffix == '.7z' else path_resolved
523
- # with install_n_import("py7zr").SevenZipFile(file=path_resolved, mode=mode, password=pwd) as archive: archive.writeall(path=str(slf), arcname=None)
524
483
  arcname_obj = PathExtended(arcname or slf.name)
525
484
  if arcname_obj.name != slf.name:
526
485
  arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
@@ -593,15 +552,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
593
552
  folder = folder if not content else folder.parent
594
553
  if slf.suffix == ".7z":
595
554
  raise NotImplementedError("I have not implemented this yet")
596
- # if overwrite: P(folder).delete(sure=True)
597
- # result = folder
598
- # import py7zr
599
- # with py7zr.SevenZipFile(file=slf, mode='r', password=pwd) as archive:
600
- # if pattern is not None:
601
- # import re
602
- # pat = re.compile(pattern)
603
- # archive.extract(path=folder, targets=[f for f in archive.getnames() if pat.match(f)])
604
- # else: archive.extractall(path=folder)
605
555
  else:
606
556
  if overwrite:
607
557
  if not content:
@@ -736,19 +686,52 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
736
686
  return ret
737
687
 
738
688
  def decompress(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
739
- if ".tar.gz" in str(self) or ".tgz" in str(self):
689
+ if str(self).endswith(".tar.gz") or str(self).endswith(".tgz"):
740
690
  # res = self.ungz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
741
691
  return self.ungz(name=f"tmp_{randstr()}.tar", inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose) # this works for .tgz suffix as well as .tar.gz
742
- elif ".gz" in str(self):
692
+ elif str(self).endswith(".tar"):
693
+ res = self.untar(folder=folder, name=name, path=path, inplace=inplace, orig=orig, verbose=verbose)
694
+ elif str(self).endswith(".gz"):
743
695
  res = self.ungz(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
744
- elif ".tar.bz" in str(self) or "tbz" in str(self):
696
+ elif str(self).endswith(".tar.bz") or str(self).endswith(".tbz"):
745
697
  res = self.unbz(name=f"tmp_{randstr()}.tar", inplace=inplace)
746
698
  return res.untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
747
- elif ".tar.xz" in str(self):
699
+ elif str(self).endswith(".tar.xz"):
748
700
  # res = self.unxz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
749
701
  res = self.unxz(inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
750
- elif ".zip" in str(self):
702
+ elif str(self).endswith(".zip"):
751
703
  res = self.unzip(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
704
+ elif str(self).endswith(".7z"):
705
+ def unzip_7z(archive_path: str, dest_dir: Optional[str] = None) -> Path:
706
+ """
707
+ Uncompresses a .7z archive to a directory and returns the Path to the extraction directory.
708
+
709
+ :param archive_path: path to the .7z archive file
710
+ :param dest_dir: optional path to directory to extract into; if None a temporary dir will be created
711
+ :return: pathlib.Path pointing to the destination directory where contents were extracted
712
+ :raises: FileNotFoundError if archive does not exist; py7zr.Bad7zFile or other error if extraction fails
713
+ """
714
+ import py7zr # type: ignore
715
+ import tempfile
716
+ from pathlib import Path
717
+ archive_path_obj = Path(archive_path)
718
+ if not archive_path_obj.is_file():
719
+ raise FileNotFoundError(f"Archive file not found: {archive_path_obj!r}")
720
+ if dest_dir is None:
721
+ # create a temporary directory
722
+ dest = Path(tempfile.mkdtemp(prefix=f"unzip7z_{archive_path_obj.stem}_"))
723
+ else:
724
+ dest = Path(dest_dir)
725
+ dest.mkdir(parents=True, exist_ok=True)
726
+ # Perform extraction
727
+ with py7zr.SevenZipFile(str(archive_path_obj), mode='r') as archive:
728
+ archive.extractall(path=str(dest))
729
+ # Return the extraction directory path
730
+ return dest
731
+ from machineconfig.utils.code import run_lambda_function
732
+ destination_dir = str(self.expanduser().resolve()).replace(".7z", "")
733
+ run_lambda_function(lambda: unzip_7z(archive_path=str(self), dest_dir=destination_dir), uv_project_dir=None, uv_with=["py7zr"])
734
+ res = PathExtended(destination_dir)
752
735
  else:
753
736
  res = self
754
737
  return res
@@ -826,7 +809,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
826
809
  path = self
827
810
  else:
828
811
  try:
829
- path = self.rel2home()
812
+ path = PathExtended(self.expanduser().absolute().relative_to(Path.home()))
830
813
  except ValueError as ve:
831
814
  if strict:
832
815
  raise ve
@@ -861,6 +844,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
861
844
  transfers: int = 10,
862
845
  root: Optional[str] = "myhome",
863
846
  ) -> "PathExtended":
847
+ _ = transfers
864
848
  to_del = []
865
849
  localpath = self.expanduser().absolute() if not self.exists() else self
866
850
  if zip:
@@ -873,22 +857,21 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
873
857
  rp = localpath.get_remote_path(root=root, os_specific=os_specific, rel2home=rel2home, strict=strict) # if rel2home else (P(root) / localpath if root is not None else localpath)
874
858
  else:
875
859
  rp = PathExtended(remotepath)
876
- rclone_cmd = f"""rclone copyto '{localpath.as_posix()}' '{cloud}:{rp.as_posix()}' {"--progress" if verbose else ""} --transfers={transfers}"""
877
- from machineconfig.utils.terminal import Terminal
860
+ from rclone_python import rclone
861
+ print(f"⬆️ UPLOADING {repr(localpath)} TO {cloud}:{rp.as_posix()}`") if verbose else None
862
+ rclone.copyto(in_path=localpath.as_posix(), out_path=f"{cloud}:{rp.as_posix()}", )
878
863
 
879
- if verbose:
880
- print(f"{'⬆️' * 5} UPLOADING with `{rclone_cmd}`")
881
- shell_to_use = "powershell" if sys.platform == "win32" else "bash"
882
- res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use).capture()
883
864
  _ = [item.delete(sure=True) for item in to_del]
884
- assert res.is_successful(strict_err=False, strict_returcode=True), res.print(capture=False, desc="Cloud Storage Operation")
885
865
  if verbose:
886
866
  print(f"{'⬆️' * 5} UPLOAD COMPLETED.")
887
867
  if share:
888
868
  if verbose:
889
869
  print("🔗 SHARING FILE")
890
870
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
891
- res = Terminal().run(f"""rclone link '{cloud}:{rp.as_posix()}'""", shell=shell_to_use).capture()
871
+ command = f"rclone link '{cloud}:{rp.as_posix()}'"
872
+ completed = _run_shell_command(command, shell_to_use)
873
+ from machineconfig.utils.terminal import Response
874
+ res = Response.from_completed_process(completed).capture()
892
875
  tmp = res.op2path(strict_err=False, strict_returncode=False)
893
876
  if tmp is None:
894
877
  res.print()
@@ -916,6 +899,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
916
899
  overwrite: bool = True,
917
900
  merge: bool = False,
918
901
  ):
902
+ _ = verbose, transfers
919
903
  if remotepath is None:
920
904
  remotepath = self.get_remote_path(root=root, os_specific=os_specific, rel2home=rel2home, strict=strict)
921
905
  remotepath += ".zip" if unzip else ""
@@ -925,16 +909,11 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
925
909
  localpath = self.expanduser().absolute()
926
910
  localpath += ".zip" if unzip else ""
927
911
  localpath += ".enc" if decrypt else ""
928
- rclone_cmd = f"""rclone copyto '{cloud}:{remotepath.as_posix()}' '{localpath.as_posix()}' {"--progress" if verbose else ""} --transfers={transfers}"""
929
- from machineconfig.utils.terminal import Terminal
930
-
931
- if verbose:
932
- print(f"{'⬇️' * 5} DOWNLOADING with `{rclone_cmd}`")
933
- shell_to_use = "powershell" if sys.platform == "win32" else "bash"
934
- res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use)
935
- success = res.is_successful(strict_err=False, strict_returcode=True)
936
- if not success:
937
- res.print(capture=False, desc="Cloud Storage Operation")
912
+ from rclone_python import rclone
913
+ try:
914
+ rclone.copyto(in_path=f"{cloud}:{remotepath.as_posix()}", out_path=localpath.as_posix(), )
915
+ except Exception as e:
916
+ print("to_cloud error", e)
938
917
  return None
939
918
  if decrypt:
940
919
  localpath = localpath.decrypt(key=key, pwd=pwd, inplace=True)
@@ -946,23 +925,34 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
946
925
  tmp_path_obj = self.expanduser().absolute()
947
926
  tmp_path_obj.parent.mkdir(parents=True, exist_ok=True)
948
927
  tmp1, tmp2 = tmp_path_obj.as_posix(), self.get_remote_path(root=root, os_specific=os_specific).as_posix()
949
- source, target = (tmp1, f"{cloud}:{tmp2 if rel2home else tmp1}") if sync_up else (f"{cloud}:{tmp2 if rel2home else tmp1}", tmp1) # in bisync direction is irrelavent.
928
+ if sync_up:
929
+ source = tmp1
930
+ target = f"{cloud}:{tmp2 if rel2home else tmp1}"
931
+ else:
932
+ source = f"{cloud}:{tmp2 if rel2home else tmp1}" # in bisync direction is irrelavent.
933
+ target = tmp1
934
+
950
935
  if not sync_down and not sync_up:
951
936
  _ = print(f"SYNCING 🔄️ {source} {'<>' * 7} {target}`") if verbose else None
952
937
  rclone_cmd = f"""rclone bisync '{source}' '{target}' --resync --remove-empty-dirs """
953
938
  else:
954
939
  print(f"SYNCING 🔄️ {source} {'>' * 15} {target}`")
955
940
  rclone_cmd = f"""rclone sync '{source}' '{target}' """
941
+
956
942
  rclone_cmd += f" --progress --transfers={transfers} --verbose"
957
943
  rclone_cmd += " --delete-during" if delete else ""
958
- from machineconfig.utils.terminal import Terminal
959
-
960
944
  if verbose:
961
945
  print(rclone_cmd)
962
946
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
963
- res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use)
947
+ stdout_target: Optional[int] = None if verbose else subprocess.PIPE
948
+ stderr_target: Optional[int] = None if verbose else subprocess.PIPE
949
+ completed = _run_shell_command(rclone_cmd, shell_to_use, stdout=stdout_target, stderr=stderr_target)
950
+ from machineconfig.utils.terminal import Response
951
+
952
+ res = Response.from_completed_process(completed)
964
953
  success = res.is_successful(strict_err=False, strict_returcode=True)
965
954
  if not success:
966
955
  res.print(capture=False, desc="Cloud Storage Operation")
967
956
  return None
957
+
968
958
  return self
@@ -1,18 +1,16 @@
1
- from machineconfig.utils.path_extended import PathExtended as PathExtended
2
- from machineconfig.utils.options import choose_from_options
3
1
  from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
4
2
  from rich.console import Console
5
3
  from rich.panel import Panel
6
4
  import platform
7
5
  import subprocess
8
6
  from pathlib import Path
9
-
7
+ from typing import Optional
10
8
 
11
9
  console = Console()
12
10
 
13
11
 
14
- def sanitize_path(a_path: str) -> PathExtended:
15
- path = PathExtended(a_path)
12
+ def sanitize_path(a_path: str) -> Path:
13
+ path = Path(a_path)
16
14
  if Path.cwd() == Path.home() and not path.exists():
17
15
  result = input("Current working directory is home, and passed path is not full path, are you sure you want to continue, [y]/n? ") or "y"
18
16
  if result == "y":
@@ -23,13 +21,13 @@ def sanitize_path(a_path: str) -> PathExtended:
23
21
  if platform.system() == "Windows": # path copied from Linux/Mac to Windows
24
22
  # For Linux: /home/username, for Mac: /Users/username
25
23
  skip_parts = 3 if path.as_posix().startswith("/home") else 3 # Both have 3 parts to skip
26
- path = PathExtended.home().joinpath(*path.parts[skip_parts:])
24
+ path = Path.home().joinpath(*path.parts[skip_parts:])
27
25
  assert path.exists(), f"File not found: {path}"
28
26
  source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
29
27
  console.print(Panel(f"🔗 PATH MAPPING | {source_os} → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
30
- elif platform.system() in ["Linux", "Darwin"] and PathExtended.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
28
+ elif platform.system() in ["Linux", "Darwin"] and Path.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
31
29
  skip_parts = 3 # Both /home/username and /Users/username have 3 parts to skip
32
- path = PathExtended.home().joinpath(*path.parts[skip_parts:])
30
+ path = Path.home().joinpath(*path.parts[skip_parts:])
33
31
  assert path.exists(), f"File not found: {path}"
34
32
  current_os = "Linux" if platform.system() == "Linux" else "macOS"
35
33
  source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
@@ -37,12 +35,12 @@ def sanitize_path(a_path: str) -> PathExtended:
37
35
  elif path.as_posix().startswith("C:"):
38
36
  if platform.system() in ["Linux", "Darwin"]: # path copied from Windows to Linux/Mac
39
37
  xx = str(a_path).replace("\\\\", "/")
40
- path = PathExtended.home().joinpath(*PathExtended(xx).parts[3:]) # exclude C:\\Users\\username
38
+ path = Path.home().joinpath(*Path(xx).parts[3:]) # exclude C:\\Users\\username
41
39
  assert path.exists(), f"File not found: {path}"
42
40
  target_os = "Linux" if platform.system() == "Linux" else "macOS"
43
41
  console.print(Panel(f"🔗 PATH MAPPING | Windows → {target_os}: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
44
- elif platform.system() == "Windows" and PathExtended.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
45
- path = PathExtended.home().joinpath(*path.parts[2:])
42
+ elif platform.system() == "Windows" and Path.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
43
+ path = Path.home().joinpath(*path.parts[2:])
46
44
  assert path.exists(), f"File not found: {path}"
47
45
  console.print(Panel(f"🔗 PATH MAPPING | Windows → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
48
46
  return path.expanduser().absolute()
@@ -67,49 +65,58 @@ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[l
67
65
  return filename_matches, partial_path_matches
68
66
 
69
67
 
70
- def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[str]) -> PathExtended:
68
+ def match_file_name(sub_string: str, search_root: Path, suffixes: set[str]) -> Path:
71
69
  search_root_obj = search_root.absolute()
72
70
  # assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
73
71
  filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string, suffixes)
74
72
  if len(filename_matches) == 1:
75
- return PathExtended(filename_matches[0])
73
+ return Path(filename_matches[0])
76
74
  console.print(Panel(f"Partial filename {search_root_obj} match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
77
75
  if len(filename_matches) < 20:
78
76
  print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
79
77
  if len(filename_matches) > 1:
80
- print("Try to narrow down filename_matches search by case-sensitivity.")
78
+ print(f"Try to narrow down filename_matches search by case-sensitivity, found {len(filename_matches)} results. First @ {filename_matches[0].as_posix()}")
81
79
  # let's see if avoiding .lower() helps narrowing down to one result
82
80
  reduced_scripts = [a_potential_match for a_potential_match in filename_matches if sub_string in a_potential_match.name]
83
81
  if len(reduced_scripts) == 1:
84
- return PathExtended(reduced_scripts[0])
82
+ return Path(reduced_scripts[0])
85
83
  elif len(reduced_scripts) > 1:
84
+ from machineconfig.utils.options import choose_from_options
86
85
  choice = choose_from_options(multi=False, msg="Multiple matches found", options=reduced_scripts, fzf=True)
87
- return PathExtended(choice)
86
+ return Path(choice)
88
87
  print(f"Result: This still generated {len(reduced_scripts)} results.")
89
88
  if len(reduced_scripts) < 10:
90
89
  print("\n".join([a_potential_match.as_posix() for a_potential_match in reduced_scripts]))
91
90
 
92
91
  console.print(Panel(f"Partial path match with case-insensitivity failed. This generated #{len(partial_path_matches)} results.", title="Search", expand=False))
93
92
  if len(partial_path_matches) == 1:
94
- return PathExtended(partial_path_matches[0])
93
+ return Path(partial_path_matches[0])
95
94
  elif len(partial_path_matches) > 1:
96
95
  print("Try to narrow down partial_path_matches search by case-sensitivity.")
97
96
  reduced_scripts = [a_potential_match for a_potential_match in partial_path_matches if sub_string in a_potential_match.as_posix()]
98
97
  if len(reduced_scripts) == 1:
99
- return PathExtended(reduced_scripts[0])
100
- print(f"Result: This still generated {len(reduced_scripts)} results.")
98
+ return Path(reduced_scripts[0])
99
+ print(f"Result: This still generated {len(reduced_scripts)} results.")
100
+
101
101
  try:
102
- fzf_cmd = f"cd '{search_root_obj}'; fd --type file --strip-cwd-prefix | fzf --ignore-case --exact --query={sub_string}"
102
+
103
+ if len(partial_path_matches) == 0:
104
+ print("No partial path matches found, trying to do fd with --no-ignore ...")
105
+ fzf_cmd = f"cd '{search_root_obj}'; fd --no-ignore --type file --strip-cwd-prefix | fzf --ignore-case --exact --query={sub_string}"
106
+ else:
107
+ fzf_cmd = f"cd '{search_root_obj}'; fd --type file --strip-cwd-prefix | fzf --ignore-case --exact --query={sub_string}"
103
108
  console.print(Panel(f"🔍 Second attempt: SEARCH STRATEGY | Using fd to search for '{sub_string}' in '{search_root_obj}' ...\n{fzf_cmd}", title="Search Strategy", expand=False))
104
109
  search_res_raw = subprocess.run(fzf_cmd, stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout
105
- search_res = search_res_raw.strip().split("\\n")[:-1]
110
+ search_res = search_res_raw.strip().split("\n")
106
111
  except subprocess.CalledProcessError as cpe:
107
112
  console.print(Panel(f"❌ ERROR | FZF search failed with '{sub_string}' in '{search_root_obj}'.\n{cpe}", title="Error", expand=False))
108
113
  import sys
109
-
110
114
  sys.exit(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results.")
111
115
  if len(search_res) == 1:
112
116
  return search_root_obj.joinpath(search_res_raw)
117
+ elif len(search_res) == 0:
118
+ msg = Panel(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results", title="File Not Found", expand=False)
119
+ raise FileNotFoundError(msg)
113
120
 
114
121
  print(f"⚠️ WARNING | Multiple search results found for `{sub_string}`:\n'{search_res}'")
115
122
  cmd = f"cd '{search_root_obj}'; fd --type file | fzf --select-1 --query={sub_string}"
@@ -121,3 +128,49 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
121
128
  msg = Panel(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results", title="File Not Found", expand=False)
122
129
  raise FileNotFoundError(msg) from cpe
123
130
  return search_root_obj.joinpath(res)
131
+
132
+
133
+ def search_for_files_of_interest(path_obj: Path, suffixes: set[str]) -> list[Path]:
134
+ if path_obj.is_file():
135
+ return [path_obj]
136
+ files: list[Path] = []
137
+ directories_to_visit: list[Path] = [path_obj]
138
+ while directories_to_visit:
139
+ current_dir = directories_to_visit.pop()
140
+ for entry in current_dir.iterdir():
141
+ if entry.is_dir():
142
+ if entry.name == ".venv":
143
+ continue
144
+ directories_to_visit.append(entry)
145
+ continue
146
+ if entry.suffix not in suffixes:
147
+ continue
148
+ if entry.suffix == ".py" and entry.name == "__init__.py":
149
+ continue
150
+ files.append(entry)
151
+ return files
152
+
153
+
154
+ def get_choice_file(path: str, suffixes: Optional[set[str]]):
155
+ path_obj = sanitize_path(path)
156
+ if suffixes is None:
157
+ import platform
158
+ if platform.system() == "Windows":
159
+ suffixes = {".py", ".ps1", ".sh"}
160
+ elif platform.system() in ["Linux", "Darwin"]:
161
+ suffixes = {".py", ".sh"}
162
+ else:
163
+ suffixes = {".py"}
164
+ if not path_obj.exists():
165
+ print(f"🔍 Searching for file matching `{path}` under `{Path.cwd()}`, but only if suffix matches {suffixes}")
166
+ choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
167
+ elif path_obj.is_dir():
168
+ print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
169
+ files = search_for_files_of_interest(path_obj, suffixes=suffixes)
170
+ print(f"🔍 Got #{len(files)} results.")
171
+ from machineconfig.utils.options import choose_from_options
172
+ choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
173
+ choice_file = Path(choice_file)
174
+ else:
175
+ choice_file = path_obj
176
+ return choice_file