machineconfig 3.99__py3-none-any.whl → 7.66__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 (418) 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 +2 -16
  32. machineconfig/jobs/installer/custom/boxes.py +61 -0
  33. machineconfig/jobs/installer/custom/gh.py +69 -53
  34. machineconfig/jobs/installer/custom/hx.py +77 -20
  35. machineconfig/jobs/installer/custom_dev/alacritty.py +45 -30
  36. machineconfig/jobs/installer/custom_dev/brave.py +43 -35
  37. machineconfig/jobs/installer/custom_dev/bypass_paywall.py +31 -20
  38. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  39. machineconfig/jobs/installer/custom_dev/code.py +33 -21
  40. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +30 -0
  41. machineconfig/jobs/installer/custom_dev/espanso.py +64 -41
  42. machineconfig/jobs/installer/custom_dev/goes.py +41 -36
  43. machineconfig/jobs/installer/custom_dev/lvim.py +49 -33
  44. machineconfig/jobs/installer/custom_dev/nerdfont.py +71 -47
  45. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +32 -26
  46. machineconfig/jobs/installer/custom_dev/redis.py +51 -33
  47. machineconfig/jobs/installer/custom_dev/sysabc.py +119 -0
  48. machineconfig/jobs/installer/custom_dev/wezterm.py +55 -39
  49. machineconfig/jobs/installer/custom_dev/winget.py +1 -0
  50. machineconfig/jobs/installer/installer_data.json +3406 -0
  51. machineconfig/jobs/installer/linux_scripts/brave.sh +4 -14
  52. machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +5 -17
  53. machineconfig/jobs/installer/linux_scripts/docker.sh +5 -17
  54. machineconfig/jobs/installer/linux_scripts/docker_start.sh +6 -14
  55. machineconfig/jobs/installer/linux_scripts/edge.sh +3 -11
  56. machineconfig/jobs/{linux/msc → installer/linux_scripts}/lid.sh +2 -8
  57. machineconfig/jobs/installer/linux_scripts/nerdfont.sh +5 -17
  58. machineconfig/jobs/{linux/msc → installer/linux_scripts}/network.sh +2 -8
  59. machineconfig/jobs/installer/linux_scripts/ngrok.sh +6 -0
  60. machineconfig/jobs/installer/linux_scripts/q.sh +9 -0
  61. machineconfig/jobs/installer/linux_scripts/redis.sh +6 -17
  62. machineconfig/jobs/installer/linux_scripts/vscode.sh +5 -17
  63. machineconfig/jobs/installer/linux_scripts/wezterm.sh +4 -12
  64. machineconfig/jobs/installer/package_groups.py +255 -0
  65. machineconfig/logger.py +0 -1
  66. machineconfig/profile/backup.toml +49 -0
  67. machineconfig/profile/bash_shell_profiles.md +11 -0
  68. machineconfig/profile/create_helper.py +74 -0
  69. machineconfig/profile/create_links.py +288 -0
  70. machineconfig/profile/create_links_export.py +100 -0
  71. machineconfig/profile/create_shell_profile.py +136 -0
  72. machineconfig/profile/mapper.toml +258 -0
  73. machineconfig/scripts/Restore-ThunderbirdProfile.ps1 +92 -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/initai.py +3 -28
  83. machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +17 -18
  84. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +17 -18
  85. machineconfig/scripts/python/ai/solutions/_shared.py +9 -1
  86. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +1 -1
  87. machineconfig/scripts/python/ai/solutions/copilot/prompts/pyright_fix.md +16 -0
  88. machineconfig/scripts/python/ai/solutions/generic.py +27 -4
  89. machineconfig/scripts/python/ai/vscode_tasks.py +37 -0
  90. machineconfig/scripts/python/cloud.py +29 -0
  91. machineconfig/scripts/python/croshell.py +117 -181
  92. machineconfig/scripts/python/define.py +31 -0
  93. machineconfig/scripts/python/devops.py +44 -124
  94. machineconfig/scripts/python/devops_navigator.py +10 -0
  95. machineconfig/scripts/python/env_manager/__init__.py +1 -0
  96. machineconfig/scripts/python/env_manager/path_manager_backend.py +47 -0
  97. machineconfig/scripts/python/env_manager/path_manager_tui.py +228 -0
  98. machineconfig/scripts/python/explore.py +49 -0
  99. machineconfig/scripts/python/fire_jobs.py +106 -244
  100. machineconfig/scripts/python/ftpx.py +125 -68
  101. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.json +14 -0
  102. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.py +37 -0
  103. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_cursor_agents.py +22 -0
  104. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_gemini.py +42 -0
  105. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +30 -0
  106. machineconfig/scripts/python/helpers_agents/fire_agents_help_launch.py +110 -0
  107. machineconfig/scripts/python/helpers_agents/fire_agents_helper_types.py +34 -0
  108. machineconfig/scripts/python/helpers_agents/fire_agents_load_balancer.py +22 -0
  109. machineconfig/scripts/python/helpers_agents/templates/prompt.txt +6 -0
  110. machineconfig/scripts/python/helpers_agents/templates/template.ps1 +14 -0
  111. machineconfig/scripts/python/helpers_agents/templates/template.sh +24 -0
  112. machineconfig/scripts/python/{cloud_copy.py → helpers_cloud/cloud_copy.py} +30 -23
  113. machineconfig/scripts/python/{cloud_mount.py → helpers_cloud/cloud_mount.py} +11 -19
  114. machineconfig/scripts/python/{cloud_sync.py → helpers_cloud/cloud_sync.py} +12 -18
  115. machineconfig/scripts/python/{helpers → helpers_cloud}/helpers2.py +3 -3
  116. machineconfig/scripts/python/helpers_croshell/crosh.py +39 -0
  117. machineconfig/scripts/python/{start_slidev.py → helpers_croshell/start_slidev.py} +17 -7
  118. machineconfig/scripts/python/helpers_devops/cli_config.py +95 -0
  119. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +89 -0
  120. machineconfig/scripts/python/helpers_devops/cli_data.py +25 -0
  121. machineconfig/scripts/python/helpers_devops/cli_nw.py +134 -0
  122. machineconfig/scripts/python/helpers_devops/cli_repos.py +182 -0
  123. machineconfig/scripts/python/helpers_devops/cli_self.py +134 -0
  124. machineconfig/scripts/python/helpers_devops/cli_share_file.py +137 -0
  125. machineconfig/scripts/python/helpers_devops/cli_share_server.py +141 -0
  126. machineconfig/scripts/python/helpers_devops/cli_terminal.py +156 -0
  127. machineconfig/scripts/python/helpers_devops/cli_utils.py +96 -0
  128. machineconfig/scripts/python/{devops_backup_retrieve.py → helpers_devops/devops_backup_retrieve.py} +7 -10
  129. machineconfig/scripts/python/helpers_devops/devops_status.py +511 -0
  130. machineconfig/scripts/python/helpers_devops/devops_update_repos.py +269 -0
  131. machineconfig/scripts/python/helpers_devops/themes/choose_pwsh_theme.ps1 +81 -0
  132. machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +3 -0
  133. machineconfig/scripts/python/{choose_wezterm_theme.py → helpers_devops/themes/choose_wezterm_theme.py} +2 -2
  134. machineconfig/scripts/python/helpers_fire_command/__init__.py +0 -0
  135. machineconfig/scripts/python/{helpers/helpers4.py → helpers_fire_command/file_wrangler.py} +57 -87
  136. machineconfig/scripts/python/helpers_fire_command/fire_jobs_args_helper.py +145 -0
  137. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +110 -0
  138. machineconfig/scripts/python/helpers_fire_command/fire_jobs_streamlit_helper.py +0 -0
  139. machineconfig/scripts/python/helpers_msearch/__init__.py +5 -0
  140. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfag +1 -1
  141. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfg +1 -1
  142. machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfrga +1 -1
  143. machineconfig/scripts/python/helpers_navigator/__init__.py +20 -0
  144. machineconfig/scripts/python/helpers_navigator/command_builder.py +111 -0
  145. machineconfig/scripts/python/helpers_navigator/command_detail.py +44 -0
  146. machineconfig/scripts/python/helpers_navigator/command_tree.py +588 -0
  147. machineconfig/scripts/python/helpers_navigator/data_models.py +28 -0
  148. machineconfig/scripts/python/helpers_navigator/main_app.py +272 -0
  149. machineconfig/scripts/python/helpers_navigator/search_bar.py +15 -0
  150. machineconfig/scripts/python/helpers_repos/action.py +209 -0
  151. machineconfig/scripts/python/helpers_repos/action_helper.py +150 -0
  152. machineconfig/scripts/python/{repos_helper_clone.py → helpers_repos/clone.py} +6 -7
  153. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +218 -0
  154. machineconfig/scripts/python/helpers_repos/count_lines.py +348 -0
  155. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +17 -0
  156. machineconfig/scripts/python/helpers_repos/entrypoint.py +77 -0
  157. machineconfig/scripts/python/helpers_repos/grource.py +340 -0
  158. machineconfig/scripts/python/{repos_helper_record.py → helpers_repos/record.py} +7 -4
  159. machineconfig/scripts/python/helpers_repos/sync.py +66 -0
  160. machineconfig/scripts/python/{repos_helper_update.py → helpers_repos/update.py} +3 -3
  161. machineconfig/scripts/python/helpers_sessions/__init__.py +0 -0
  162. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +58 -0
  163. machineconfig/scripts/python/helpers_utils/download.py +152 -0
  164. machineconfig/scripts/python/helpers_utils/path.py +108 -0
  165. machineconfig/scripts/python/interactive.py +79 -160
  166. machineconfig/scripts/python/machineconfig.py +63 -0
  167. machineconfig/scripts/python/msearch.py +21 -0
  168. machineconfig/scripts/python/nw/__init__.py +0 -0
  169. machineconfig/scripts/python/{devops_add_identity.py → nw/devops_add_identity.py} +1 -3
  170. machineconfig/scripts/python/{devops_add_ssh_key.py → nw/devops_add_ssh_key.py} +74 -44
  171. machineconfig/scripts/{linux → python/nw}/mount_nfs +1 -1
  172. machineconfig/scripts/python/{mount_nfs.py → nw/mount_nfs.py} +19 -16
  173. machineconfig/scripts/{linux → python/nw}/mount_nw_drive +1 -2
  174. machineconfig/scripts/python/{mount_ssh.py → nw/mount_ssh.py} +7 -8
  175. machineconfig/scripts/python/{onetimeshare.py → nw/onetimeshare.py} +0 -1
  176. machineconfig/scripts/python/nw/ssh_debug_linux.py +391 -0
  177. machineconfig/scripts/python/nw/ssh_debug_windows.py +338 -0
  178. machineconfig/scripts/python/{wifi_conn.py → nw/wifi_conn.py} +1 -53
  179. machineconfig/scripts/python/{wsl_windows_transfer.py → nw/wsl_windows_transfer.py} +6 -5
  180. machineconfig/scripts/python/sessions.py +167 -0
  181. machineconfig/scripts/python/terminal.py +127 -0
  182. machineconfig/scripts/python/utils.py +66 -0
  183. machineconfig/scripts/windows/{mount_nfs.ps1 → mounts/mount_nfs.ps1} +1 -3
  184. machineconfig/scripts/windows/{mount_ssh.ps1 → mounts/mount_ssh.ps1} +1 -1
  185. machineconfig/scripts/windows/{share_smb.ps1 → mounts/share_smb.ps1} +0 -6
  186. machineconfig/scripts/windows/wrap_mcfg.ps1 +60 -0
  187. machineconfig/settings/broot/br.sh +0 -4
  188. machineconfig/settings/broot/conf.toml +1 -1
  189. machineconfig/settings/helix/config.toml +16 -0
  190. machineconfig/settings/helix/languages.toml +13 -4
  191. machineconfig/settings/helix/yazi-picker.sh +12 -0
  192. machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
  193. machineconfig/settings/lf/linux/exe/previewer.sh +9 -3
  194. machineconfig/settings/lf/linux/lfrc +10 -12
  195. machineconfig/settings/lf/windows/fzf_edit.ps1 +2 -2
  196. machineconfig/settings/lf/windows/lfrc +18 -38
  197. machineconfig/settings/lf/windows/mkfile.ps1 +1 -1
  198. machineconfig/settings/linters/.ruff.toml +1 -1
  199. machineconfig/settings/lvim/windows/archive/config_additional.lua +0 -6
  200. machineconfig/settings/marimo/marimo.toml +80 -0
  201. machineconfig/settings/marimo/snippets/globalize.py +34 -0
  202. machineconfig/settings/pistol/pistol.conf +1 -1
  203. machineconfig/settings/shells/bash/init.sh +55 -31
  204. machineconfig/settings/shells/nushell/config.nu +1 -34
  205. machineconfig/settings/shells/nushell/init.nu +127 -0
  206. machineconfig/settings/shells/pwsh/init.ps1 +60 -43
  207. machineconfig/settings/shells/starship/starship.toml +16 -0
  208. machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
  209. machineconfig/settings/shells/wt/settings.json +32 -17
  210. machineconfig/settings/shells/zsh/init.sh +89 -0
  211. machineconfig/settings/svim/linux/init.toml +0 -4
  212. machineconfig/settings/svim/windows/init.toml +0 -3
  213. machineconfig/settings/yazi/init.lua +57 -0
  214. machineconfig/settings/yazi/keymap_linux.toml +79 -0
  215. machineconfig/settings/yazi/keymap_windows.toml +78 -0
  216. machineconfig/settings/yazi/shell/yazi_cd.ps1 +33 -0
  217. machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
  218. machineconfig/settings/yazi/yazi.toml +13 -0
  219. machineconfig/setup_linux/__init__.py +10 -0
  220. machineconfig/setup_linux/apps_desktop.sh +89 -0
  221. machineconfig/setup_linux/apps_gui.sh +64 -0
  222. machineconfig/setup_linux/{nix → others}/cli_installation.sh +9 -29
  223. machineconfig/setup_linux/ssh/openssh_all.sh +25 -0
  224. machineconfig/setup_linux/ssh/openssh_wsl.sh +38 -0
  225. machineconfig/setup_linux/uv.sh +15 -0
  226. machineconfig/setup_linux/web_shortcuts/interactive.sh +26 -6
  227. machineconfig/setup_mac/__init__.py +16 -0
  228. machineconfig/setup_mac/apps_gui.sh +248 -0
  229. machineconfig/setup_mac/ssh/openssh_setup.sh +114 -0
  230. machineconfig/setup_mac/uv.sh +36 -0
  231. machineconfig/setup_windows/__init__.py +8 -0
  232. machineconfig/setup_windows/others/power_options.ps1 +7 -0
  233. machineconfig/setup_windows/ssh/add-sshkey.ps1 +29 -0
  234. machineconfig/setup_windows/ssh/add_identity.ps1 +11 -0
  235. machineconfig/setup_windows/ssh/openssh-server.ps1 +37 -0
  236. machineconfig/setup_windows/uv.ps1 +10 -0
  237. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +27 -9
  238. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +16 -0
  239. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +37 -23
  240. machineconfig/utils/accessories.py +7 -5
  241. machineconfig/utils/cloud/onedrive/README.md +139 -0
  242. machineconfig/utils/code.py +140 -93
  243. machineconfig/utils/files/art/fat_croco.txt +10 -0
  244. machineconfig/utils/files/art/halfwit_croco.txt +9 -0
  245. machineconfig/utils/files/art/happy_croco.txt +22 -0
  246. machineconfig/utils/files/art/water_croco.txt +11 -0
  247. machineconfig/utils/files/ascii_art.py +118 -0
  248. machineconfig/utils/files/dbms.py +257 -0
  249. machineconfig/utils/files/headers.py +68 -0
  250. machineconfig/utils/files/ouch/__init__.py +0 -0
  251. machineconfig/utils/files/ouch/decompress.py +45 -0
  252. machineconfig/utils/files/read.py +95 -0
  253. machineconfig/utils/installer_utils/github_release_bulk.py +2 -12
  254. machineconfig/utils/installer_utils/installer_class.py +68 -126
  255. machineconfig/utils/installer_utils/installer_cli.py +181 -0
  256. machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +38 -85
  257. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +69 -69
  258. machineconfig/utils/io.py +77 -23
  259. machineconfig/utils/links.py +309 -100
  260. machineconfig/utils/meta.py +255 -0
  261. machineconfig/utils/notifications.py +1 -1
  262. machineconfig/utils/options.py +10 -25
  263. machineconfig/utils/path_extended.py +94 -104
  264. machineconfig/utils/path_helper.py +75 -22
  265. machineconfig/utils/procs.py +50 -74
  266. machineconfig/utils/scheduler.py +94 -97
  267. machineconfig/utils/scheduling.py +0 -3
  268. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +5 -17
  269. machineconfig/utils/schemas/installer/installer_types.py +0 -1
  270. machineconfig/utils/schemas/layouts/layout_types.py +2 -1
  271. machineconfig/utils/source_of_truth.py +3 -6
  272. machineconfig/utils/ssh.py +742 -254
  273. machineconfig/utils/ssh_utils/utils.py +0 -0
  274. machineconfig/utils/terminal.py +3 -140
  275. machineconfig/utils/tst.py +20 -0
  276. machineconfig/utils/upgrade_packages.py +109 -28
  277. machineconfig/utils/ve.py +13 -5
  278. machineconfig-7.66.dist-info/METADATA +124 -0
  279. machineconfig-7.66.dist-info/RECORD +451 -0
  280. machineconfig-7.66.dist-info/entry_points.txt +15 -0
  281. machineconfig/cluster/templates/utils.py +0 -51
  282. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -49
  283. machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -85
  284. machineconfig/jobs/installer/packages_custom_dev.json +0 -226
  285. machineconfig/jobs/installer/packages_custom_essential.json +0 -39
  286. machineconfig/jobs/installer/packages_github_dev.json +0 -1110
  287. machineconfig/jobs/installer/packages_github_essential.json +0 -804
  288. machineconfig/jobs/linux/msc/cli_agents.sh +0 -37
  289. machineconfig/jobs/python/create_bootable_media.py +0 -16
  290. machineconfig/jobs/python/python_cargo_build_share.py +0 -59
  291. machineconfig/jobs/python/python_ve_symlink.py +0 -29
  292. machineconfig/jobs/python/tasks.py +0 -3
  293. machineconfig/jobs/python/vscode/api.py +0 -49
  294. machineconfig/jobs/python/vscode/sync_code.py +0 -58
  295. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +0 -14
  296. machineconfig/jobs/windows/start_terminal.ps1 +0 -6
  297. machineconfig/jobs/windows/startup_file.cmd +0 -2
  298. machineconfig/profile/create.py +0 -170
  299. machineconfig/profile/shell.py +0 -176
  300. machineconfig/scripts/cloud/init.sh +0 -119
  301. machineconfig/scripts/linux/choose_wezterm_theme +0 -3
  302. machineconfig/scripts/linux/cloud_copy +0 -2
  303. machineconfig/scripts/linux/cloud_mount +0 -2
  304. machineconfig/scripts/linux/cloud_repo_sync +0 -2
  305. machineconfig/scripts/linux/cloud_sync +0 -2
  306. machineconfig/scripts/linux/croshell +0 -3
  307. machineconfig/scripts/linux/devops +0 -2
  308. machineconfig/scripts/linux/fire +0 -2
  309. machineconfig/scripts/linux/fire_agents +0 -2
  310. machineconfig/scripts/linux/ftpx +0 -2
  311. machineconfig/scripts/linux/fzf2g +0 -21
  312. machineconfig/scripts/linux/fzffg +0 -25
  313. machineconfig/scripts/linux/gh_models +0 -2
  314. machineconfig/scripts/linux/initai +0 -2
  315. machineconfig/scripts/linux/kill_process +0 -2
  316. machineconfig/scripts/linux/programs +0 -21
  317. machineconfig/scripts/linux/repos +0 -2
  318. machineconfig/scripts/linux/scheduler +0 -2
  319. machineconfig/scripts/linux/share_smb +0 -1
  320. machineconfig/scripts/linux/start_slidev +0 -2
  321. machineconfig/scripts/linux/start_terminals +0 -3
  322. machineconfig/scripts/linux/warp-cli.sh +0 -122
  323. machineconfig/scripts/linux/wifi_conn +0 -2
  324. machineconfig/scripts/linux/z_ls +0 -104
  325. machineconfig/scripts/python/ai/solutions/copilot/prompts/allLintersAndTypeCheckers.prompt.md +0 -5
  326. machineconfig/scripts/python/cloud_repo_sync.py +0 -186
  327. machineconfig/scripts/python/devops_devapps_install.py +0 -159
  328. machineconfig/scripts/python/devops_update_repos.py +0 -180
  329. machineconfig/scripts/python/dotfile.py +0 -52
  330. machineconfig/scripts/python/fire_agents.py +0 -175
  331. machineconfig/scripts/python/fire_agents_help_launch.py +0 -143
  332. machineconfig/scripts/python/fire_agents_load_balancer.py +0 -50
  333. machineconfig/scripts/python/fire_jobs_args_helper.py +0 -75
  334. machineconfig/scripts/python/fire_jobs_layout_helper.py +0 -74
  335. machineconfig/scripts/python/get_zellij_cmd.py +0 -15
  336. machineconfig/scripts/python/gh_models.py +0 -104
  337. machineconfig/scripts/python/helpers/repo_sync_helpers.py +0 -114
  338. machineconfig/scripts/python/repos.py +0 -80
  339. machineconfig/scripts/python/repos_helper_action.py +0 -335
  340. machineconfig/scripts/python/share_terminal.py +0 -104
  341. machineconfig/scripts/python/snapshot.py +0 -25
  342. machineconfig/scripts/python/start_terminals.py +0 -121
  343. machineconfig/scripts/python/t4.py +0 -17
  344. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +0 -1
  345. machineconfig/scripts/windows/cloud_copy.ps1 +0 -1
  346. machineconfig/scripts/windows/cloud_mount.ps1 +0 -1
  347. machineconfig/scripts/windows/cloud_repo_sync.ps1 +0 -1
  348. machineconfig/scripts/windows/cloud_sync.ps1 +0 -1
  349. machineconfig/scripts/windows/croshell.ps1 +0 -1
  350. machineconfig/scripts/windows/devops.ps1 +0 -1
  351. machineconfig/scripts/windows/dotfile.ps1 +0 -1
  352. machineconfig/scripts/windows/fire.ps1 +0 -1
  353. machineconfig/scripts/windows/ftpx.ps1 +0 -1
  354. machineconfig/scripts/windows/gpt.ps1 +0 -1
  355. machineconfig/scripts/windows/grep.ps1 +0 -2
  356. machineconfig/scripts/windows/initai.ps1 +0 -1
  357. machineconfig/scripts/windows/kill_process.ps1 +0 -1
  358. machineconfig/scripts/windows/nano.ps1 +0 -3
  359. machineconfig/scripts/windows/pomodoro.ps1 +0 -1
  360. machineconfig/scripts/windows/reload_path.ps1 +0 -3
  361. machineconfig/scripts/windows/repos.ps1 +0 -1
  362. machineconfig/scripts/windows/scheduler.ps1 +0 -1
  363. machineconfig/scripts/windows/snapshot.ps1 +0 -1
  364. machineconfig/scripts/windows/start_slidev.ps1 +0 -1
  365. machineconfig/scripts/windows/start_terminals.ps1 +0 -1
  366. machineconfig/scripts/windows/wifi_conn.ps1 +0 -2
  367. machineconfig/scripts/windows/wsl_rdp_windows_port_forwarding.ps1 +0 -46
  368. machineconfig/scripts/windows/wsl_ssh_windows_port_forwarding.ps1 +0 -76
  369. machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
  370. machineconfig/setup_linux/others/openssh-server_add_pub_key.sh +0 -57
  371. machineconfig/setup_linux/web_shortcuts/ascii_art.sh +0 -93
  372. machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -11
  373. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -52
  374. machineconfig/setup_windows/web_shortcuts/all.ps1 +0 -18
  375. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +0 -36
  376. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +0 -16
  377. machineconfig/setup_windows/web_shortcuts/ssh.ps1 +0 -11
  378. machineconfig/utils/ai/generate_file_checklist.py +0 -68
  379. machineconfig-3.99.dist-info/METADATA +0 -167
  380. machineconfig-3.99.dist-info/RECORD +0 -409
  381. machineconfig-3.99.dist-info/entry_points.txt +0 -18
  382. machineconfig/cluster/{templates → remote}/run_cloud.py +0 -0
  383. machineconfig/cluster/{templates → remote}/run_cluster.py +0 -0
  384. machineconfig/cluster/{templates → remote}/run_remote.py +0 -0
  385. machineconfig/scripts/linux/{share_nfs → other/share_nfs} +0 -0
  386. machineconfig/scripts/linux/{start_docker → other/start_docker} +0 -0
  387. machineconfig/scripts/linux/{switch_ip → other/switch_ip} +0 -0
  388. machineconfig/{jobs/python → scripts/python/helpers_agents}/__init__.py +0 -0
  389. machineconfig/scripts/python/{helpers → helpers_agents/agentic_frameworks}/__init__.py +0 -0
  390. machineconfig/scripts/python/{fire_agents_help_search.py → helpers_agents/fire_agents_help_search.py} +0 -0
  391. machineconfig/{jobs/windows/msc/cli_agents.bat → scripts/python/helpers_cloud/__init__.py} +0 -0
  392. machineconfig/scripts/python/{helpers → helpers_cloud}/cloud_helpers.py +1 -1
  393. /machineconfig/scripts/python/{helpers → helpers_cloud}/helpers5.py +0 -0
  394. /machineconfig/{jobs/windows/msc/cli_agents.ps1 → scripts/python/helpers_croshell/__init__.py} +0 -0
  395. /machineconfig/scripts/python/{pomodoro.py → helpers_croshell/pomodoro.py} +0 -0
  396. /machineconfig/scripts/python/{scheduler.py → helpers_croshell/scheduler.py} +0 -0
  397. /machineconfig/scripts/python/{viewer.py → helpers_croshell/viewer.py} +0 -0
  398. /machineconfig/scripts/python/{viewer_template.py → helpers_croshell/viewer_template.py} +0 -0
  399. /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → helpers_devops/__init__.py} +0 -0
  400. /machineconfig/scripts/{windows/share_nfs.ps1 → python/helpers_devops/themes/__init__.py} +0 -0
  401. /machineconfig/{settings/yazi/keymap.toml → scripts/python/helpers_devops/themes/choose_starship_theme.ps1} +0 -0
  402. /machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -0
  403. /machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/skrg +0 -0
  404. /machineconfig/scripts/{windows → python/helpers_msearch/scripts_windows}/fzfb.ps1 +0 -0
  405. /machineconfig/scripts/{windows → python/helpers_msearch/scripts_windows}/fzfg.ps1 +0 -0
  406. /machineconfig/scripts/{windows → python/helpers_msearch/scripts_windows}/fzfrga.bat +0 -0
  407. /machineconfig/scripts/{linux → python/nw}/mount_drive +0 -0
  408. /machineconfig/scripts/python/{mount_nw_drive.py → nw/mount_nw_drive.py} +0 -0
  409. /machineconfig/scripts/{linux → python/nw}/mount_smb +0 -0
  410. /machineconfig/scripts/windows/{mount_nw.ps1 → mounts/mount_nw.ps1} +0 -0
  411. /machineconfig/scripts/windows/{mount_smb.ps1 → mounts/mount_smb.ps1} +0 -0
  412. /machineconfig/scripts/windows/{share_cloud.cmd → mounts/share_cloud.cmd} +0 -0
  413. /machineconfig/scripts/windows/{unlock_bitlocker.ps1 → mounts/unlock_bitlocker.ps1} +0 -0
  414. /machineconfig/setup_linux/{web_shortcuts → others}/android.sh +0 -0
  415. /machineconfig/{jobs/windows/archive → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
  416. /machineconfig/{jobs/windows/archive → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
  417. {machineconfig-3.99.dist-info → machineconfig-7.66.dist-info}/WHEEL +0 -0
  418. {machineconfig-3.99.dist-info → machineconfig-7.66.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,255 @@
1
+ """Metaprogramming utilities for analyzing and serializing Python functions."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+
7
+ def get_import_module_string(py_file: str) -> str:
8
+ from machineconfig.scripts.python.helpers_fire_command.file_wrangler import get_import_module_code
9
+ from machineconfig.utils.accessories import get_repo_root
10
+ from pathlib import Path
11
+ repo_root = get_repo_root(Path(py_file))
12
+ import_line = get_import_module_code(py_file)
13
+ if repo_root is not None:
14
+ repo_root_add = f"""sys.path.append(r'{repo_root}')"""
15
+ else:
16
+ repo_root_add = ""
17
+ txt: str = f"""
18
+ try:
19
+ {import_line}
20
+ except (ImportError, ModuleNotFoundError) as ex:
21
+ print(fr"❌ Failed to import `{py_file}` as a module: {{ex}} ")
22
+ print(fr"⚠️ Attempting import with ad-hoc `$PATH` manipulation. DO NOT pickle any objects in this session as correct deserialization cannot be guaranteed.")
23
+ import sys
24
+ sys.path.append(r'{Path(py_file).parent}')
25
+ {repo_root_add}
26
+ from {Path(py_file).stem} import *
27
+ print(fr"✅ Successfully imported `{py_file}`")
28
+ """
29
+ return txt
30
+
31
+
32
+ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_module: bool) -> str:
33
+ """
34
+ caveats: always use keyword arguments in the lambda call for best results.
35
+ return statement not allowed in the wrapped function (otherwise it can be put in the global space)
36
+ type hint in kwargs has nothing that is not built in, e.g. Optional will not work as it requires an import.
37
+
38
+ Given a no-arg lambda like `lambda: func(a=var1, b=var2)`,
39
+ return a string containing the full function definition of `func`
40
+ but with the defaults for the parameters provided in the call replaced
41
+ by the *actual* values (repr) taken from the lambda's globals.
42
+
43
+ All imports are local to this function.
44
+
45
+ Args:
46
+ lmb: A lambda function with no arguments
47
+ in_global: If True, return kwargs as global variable assignments followed by dedented body.
48
+ If False, return the full function definition with updated defaults.
49
+ import_module: When True, prepend module import bootstrap code for the function's source file.
50
+ """
51
+ # local imports
52
+ import inspect as _inspect
53
+ import ast as _ast
54
+ import textwrap as _textwrap
55
+ import types as _types
56
+ from pathlib import Path as _Path
57
+
58
+ def _stringify_annotation(annotation: Any) -> Any:
59
+ if annotation is _inspect.Signature.empty or annotation is _inspect.Parameter.empty:
60
+ return annotation
61
+ if isinstance(annotation, str):
62
+ return annotation
63
+ try:
64
+ return _inspect.formatannotation(annotation)
65
+ except Exception:
66
+ return str(annotation)
67
+
68
+ # sanity checks
69
+ if not (callable(lmb) and isinstance(lmb, _types.LambdaType)):
70
+ raise TypeError("Expected a lambda function object")
71
+
72
+ src = _inspect.getsource(lmb)
73
+ src = _textwrap.dedent(src)
74
+ tree = _ast.parse(src)
75
+
76
+ # find first Lambda node
77
+ lambda_node = None
78
+ for n in _ast.walk(tree):
79
+ if isinstance(n, _ast.Lambda):
80
+ lambda_node = n
81
+ break
82
+ if lambda_node is None:
83
+ raise ValueError("Could not find a lambda expression in source")
84
+
85
+ body = lambda_node.body
86
+ if not isinstance(body, _ast.Call):
87
+ raise ValueError("Lambda body is not a call expression")
88
+
89
+ globals_dict = getattr(lmb, "__globals__", {})
90
+
91
+ # Also capture closure variables from the lambda
92
+ closure_dict: dict[str, Any] = {}
93
+ if lmb.__closure__:
94
+ code_obj = lmb.__code__
95
+ freevars = code_obj.co_freevars
96
+ for i, var_name in enumerate(freevars):
97
+ closure_dict[var_name] = lmb.__closure__[i].cell_contents
98
+
99
+ # Merge globals and closures (closures take precedence for shadowing)
100
+ eval_namespace = {**globals_dict, **closure_dict}
101
+
102
+ # resolve the function object being called
103
+ try:
104
+ func_ref_src = _ast.unparse(body.func)
105
+ except AttributeError:
106
+ func_ref_src = _ast.get_source_segment(src, body.func) or ""
107
+ try:
108
+ func_obj = eval(func_ref_src, eval_namespace)
109
+ except Exception as e:
110
+ raise RuntimeError(f"Could not resolve function reference '{func_ref_src}': {e}")
111
+
112
+ if not callable(func_obj):
113
+ raise TypeError("Resolved object is not callable")
114
+
115
+ func_name = getattr(func_obj, "__name__", "<unknown>")
116
+
117
+ import_prefix: str = ""
118
+ if import_module:
119
+ module_file = _inspect.getsourcefile(func_obj)
120
+ module_path_candidate: str = module_file if module_file is not None else _inspect.getfile(func_obj)
121
+ import_prefix = get_import_module_string(str(_Path(module_path_candidate)))
122
+
123
+ # Evaluate each keyword argument value in the lambda's globals to get real Python objects
124
+ call_kwargs: dict[str, Any] = {}
125
+ for kw in body.keywords:
126
+ if kw.arg is None:
127
+ # **kwargs in call — evaluate to dict and merge
128
+ try:
129
+ val = eval(compile(_ast.Expression(kw.value), "<lambda_eval>", "eval"), eval_namespace)
130
+ if isinstance(val, dict):
131
+ call_kwargs.update(val)
132
+ else:
133
+ raise ValueError("Keyword expansion did not evaluate to a dict")
134
+ except Exception as e:
135
+ raise RuntimeError(f"Failed to evaluate **kwargs expression: {e}")
136
+ else:
137
+ try:
138
+ val = eval(compile(_ast.Expression(kw.value), "<lambda_eval>", "eval"), eval_namespace)
139
+ call_kwargs[kw.arg] = val
140
+ except Exception as e:
141
+ raise RuntimeError(f"Failed to evaluate value for kw '{kw.arg}': {e}")
142
+
143
+ # Try to get original source and dedent it for body extraction
144
+ try:
145
+ orig_src = _inspect.getsource(func_obj)
146
+ ded = _textwrap.dedent(orig_src)
147
+ lines = ded.splitlines()
148
+ def_index = None
149
+ for i, ln in enumerate(lines):
150
+ if ln.lstrip().startswith(f"def {func_name}("):
151
+ def_index = i
152
+ break
153
+ if def_index is None:
154
+ body_lines = ded.splitlines()
155
+ else:
156
+ signature_end_index = None
157
+ for i in range(def_index, len(lines)):
158
+ line_no_comment = lines[i].split("#", 1)[0].rstrip()
159
+ if line_no_comment.endswith(":"):
160
+ signature_end_index = i
161
+ break
162
+ if signature_end_index is None:
163
+ body_lines = lines[def_index + 1 :]
164
+ else:
165
+ body_lines = lines[signature_end_index + 1 :]
166
+ # ensure we have a body, otherwise use pass
167
+ if not any(line.strip() for line in body_lines):
168
+ body_text = " pass\n"
169
+ else:
170
+ joined_body = "\n".join(body_lines)
171
+ if not joined_body.endswith("\n"):
172
+ joined_body = f"{joined_body}\n"
173
+ body_text = joined_body
174
+ except (OSError, IOError, TypeError):
175
+ body_text = " pass\n"
176
+
177
+ # Build a replaced signature using inspect.signature
178
+ sig = _inspect.signature(func_obj)
179
+ new_params: list[_inspect.Parameter] = []
180
+ for name, param in sig.parameters.items():
181
+ # If the call provided a value for this parameter, replace default
182
+ if name in call_kwargs:
183
+ new_default = call_kwargs[name]
184
+ else:
185
+ new_default = param.default
186
+
187
+ normalized_annotation = _stringify_annotation(param.annotation)
188
+
189
+ if new_default is _inspect.Parameter.empty:
190
+ new_param = _inspect.Parameter(name, param.kind, annotation=normalized_annotation)
191
+ else:
192
+ new_param = _inspect.Parameter(
193
+ name, param.kind, default=new_default, annotation=normalized_annotation
194
+ )
195
+ new_params.append(new_param)
196
+
197
+ return_annotation = _stringify_annotation(sig.return_annotation)
198
+ new_sig = _inspect.Signature(parameters=new_params, return_annotation=return_annotation)
199
+
200
+ # If in_global mode, return kwargs as global assignments + dedented body
201
+ if in_global:
202
+ global_assignments: list[str] = []
203
+ for name, param in sig.parameters.items():
204
+ # Get the value from call_kwargs if provided, else use original default
205
+ if name in call_kwargs:
206
+ value = call_kwargs[name]
207
+ elif param.default is not _inspect.Parameter.empty:
208
+ value = param.default
209
+ else:
210
+ # No value provided and no default - skip this parameter
211
+ continue
212
+
213
+ # Build type annotation string if available
214
+ if param.annotation is not _inspect.Parameter.empty:
215
+ annotation_literal = _stringify_annotation(param.annotation)
216
+ if isinstance(annotation_literal, str):
217
+ global_assignments.append(f"{name}: {repr(annotation_literal)} = {repr(value)}")
218
+ else:
219
+ global_assignments.append(f"{name} = {repr(value)}")
220
+ else:
221
+ global_assignments.append(f"{name} = {repr(value)}")
222
+
223
+ # Dedent the body text to remove function indentation
224
+ dedented_body = _textwrap.dedent(body_text).rstrip()
225
+
226
+ # Combine global assignments and body
227
+ if global_assignments:
228
+ result_parts: list[str] = ["\n".join(global_assignments), "", dedented_body]
229
+ result_text = "\n".join(result_parts)
230
+ else:
231
+ result_text = dedented_body
232
+ else:
233
+ header = f"def {func_name}{new_sig}:\n"
234
+ result_text = header + body_text
235
+
236
+ if in_global:
237
+ lines = result_text.splitlines()
238
+ if lines[-1].startswith("return "):
239
+ lines[-1] = lines[-1].replace("return ", "# return ", 1)
240
+ result_text = "\n".join(lines)
241
+
242
+ if "Optional" in result_text or "Any" in result_text or "Union" in result_text or "Literal" in result_text:
243
+ result_text = "from typing import Optional, Any, Union, Literal\n\n" + result_text
244
+ if import_prefix:
245
+ result_text = f"{import_prefix}{result_text}"
246
+ return result_text
247
+
248
+ if __name__ == "__main__":
249
+ from machineconfig.utils.code import print_code
250
+ import_code_robust = "<import_code_robust>"
251
+ res = lambda_to_python_script(
252
+ lmb=lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
253
+ in_global=True, import_module=False
254
+ )
255
+ print(res)
@@ -110,7 +110,7 @@ encryption = ssl
110
110
 
111
111
  def send_message(self, to: str, subject: str, body: str, txt_to_html: bool = True, attachments: Optional[list[Any]] = None):
112
112
  _ = attachments
113
- body += "\n\nThis is an automated email sent via crocodile.comms script."
113
+ body += "\n\nThis is an automated email sent via machineconfig.comms script."
114
114
  # msg = message.EmailMessage()
115
115
  msg = MIMEMultipart("alternative")
116
116
  msg["subject"] = subject
@@ -1,36 +1,18 @@
1
1
  from pathlib import Path
2
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
2
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
3
3
  from rich.text import Text
4
4
  from rich.panel import Panel
5
5
  from rich.console import Console
6
6
  import subprocess
7
- from typing import Optional, Union, Iterable, overload, Literal
7
+ from typing import Optional, Union, Iterable, overload, Literal, cast
8
8
 
9
9
 
10
- # _ = cmd
11
- # cmd = "where.exe"
12
- # cmd = "which"
13
- # try: # talking to terminal is too slow.
14
- # _tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
15
- # res: bool = True
16
- # except (subprocess.CalledProcessError, FileNotFoundError):
17
- # res = False
18
- # return res
19
- # return root_path.joinpath(tool_name).is_file()
10
+ # def strip_ansi_codes(text: str) -> str:
11
+ # """Remove ANSI color codes from text."""
12
+ # import re
13
+ # return re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', text)
20
14
 
21
15
 
22
- # def choose_from_options[T](options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
23
- # choice_key = choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=False, custom_input=custom_input)
24
- # assert not isinstance(choice_key, list)
25
- # return choice_key
26
-
27
-
28
- # def choose_from_options[T](options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, custom_input: bool = False) -> list[T]:
29
- # choice_key = choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=True, multi=True, custom_input=custom_input)
30
- # if isinstance(choice_key, list):
31
- # return choice_key
32
- # return [choice_key]
33
-
34
16
  @overload
35
17
  def choose_from_options[T](msg: str, options: Iterable[T], multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
36
18
  @overload
@@ -47,17 +29,20 @@ def choose_from_options[T](msg: str, options: Iterable[T], multi: bool, custom_i
47
29
  from pyfzf.pyfzf import FzfPrompt
48
30
  fzf_prompt = FzfPrompt()
49
31
  nl = "\n"
50
- choice_string_multi: list[str] = fzf_prompt.prompt(choices=options_strings, fzf_options=("--multi" if multi else "") + f' --prompt "{prompt.replace(nl, " ")}" ') # --border-label={msg.replace(nl, ' ')}")
32
+ choice_string_multi: list[str] = fzf_prompt.prompt(choices=options_strings, fzf_options=("--multi" if multi else "") + f' --prompt "{prompt.replace(nl, " ")}" --ansi') # --border-label={msg.replace(nl, ' ')}")
51
33
  # --border=rounded doens't work on older versions of fzf installed at Ubuntu 20.04
52
34
  if not multi:
53
35
  try:
54
36
  choice_one_string = choice_string_multi[0]
37
+ if isinstance(list(options)[0], str): return cast(T, choice_one_string)
55
38
  choice_idx = options_strings.index(choice_one_string)
56
39
  return list(options)[choice_idx]
57
40
  except IndexError as ie:
58
41
  print(f"❌ Error: {options=}, {choice_string_multi=}")
59
42
  print(f"🔍 Available choices: {choice_string_multi}")
60
43
  raise ie
44
+ if isinstance(list(options)[0], str):
45
+ return cast(list[T], choice_string_multi)
61
46
  choice_idx_s = [options_strings.index(x) for x in choice_string_multi]
62
47
  return [list(options)[x] for x in choice_idx_s]
63
48
  else:
@@ -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
@@ -874,9 +857,10 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
874
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)
875
858
  else:
876
859
  rp = PathExtended(remotepath)
877
-
878
860
  from rclone_python import rclone
861
+ print(f"⬆️ UPLOADING {repr(localpath)} TO {cloud}:{rp.as_posix()}`") if verbose else None
879
862
  rclone.copyto(in_path=localpath.as_posix(), out_path=f"{cloud}:{rp.as_posix()}", )
863
+
880
864
  _ = [item.delete(sure=True) for item in to_del]
881
865
  if verbose:
882
866
  print(f"{'⬆️' * 5} UPLOAD COMPLETED.")
@@ -884,8 +868,10 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
884
868
  if verbose:
885
869
  print("🔗 SHARING FILE")
886
870
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
887
- from machineconfig.utils.terminal import Terminal
888
- 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()
889
875
  tmp = res.op2path(strict_err=False, strict_returncode=False)
890
876
  if tmp is None:
891
877
  res.print()
@@ -955,11 +941,15 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
955
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
944
  if verbose:
960
945
  print(rclone_cmd)
961
946
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
962
- 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)
963
953
  success = res.is_successful(strict_err=False, strict_returcode=True)
964
954
  if not success:
965
955
  res.print(capture=False, desc="Cloud Storage Operation")