machineconfig 7.50__py3-none-any.whl → 8.14__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 (298) hide show
  1. machineconfig/cluster/remote/cloud_manager.py +1 -1
  2. machineconfig/cluster/sessions_managers/utils/maker.py +23 -11
  3. machineconfig/cluster/sessions_managers/wt_local_manager.py +22 -19
  4. machineconfig/cluster/sessions_managers/wt_remote_manager.py +3 -1
  5. machineconfig/cluster/sessions_managers/zellij_local_manager.py +3 -1
  6. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +3 -2
  7. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +2 -2
  8. machineconfig/jobs/installer/installer_data.json +1185 -165
  9. machineconfig/jobs/installer/linux_scripts/q.sh +10 -7
  10. machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
  11. machineconfig/jobs/installer/package_groups.py +52 -84
  12. machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
  13. machineconfig/jobs/installer/{custom → python_scripts}/boxes.py +2 -2
  14. machineconfig/jobs/installer/{custom_dev → python_scripts}/brave.py +5 -3
  15. machineconfig/jobs/installer/python_scripts/cloudflare_warp_cli.py +23 -0
  16. machineconfig/jobs/installer/{custom_dev → python_scripts}/code.py +4 -1
  17. machineconfig/jobs/installer/{custom_dev → python_scripts}/dubdb_adbc.py +1 -1
  18. machineconfig/jobs/installer/{custom → python_scripts}/hx.py +16 -12
  19. machineconfig/jobs/installer/{custom_dev → python_scripts}/nerdfont.py +2 -2
  20. machineconfig/jobs/installer/{custom_dev → python_scripts}/nerfont_windows_helper.py +27 -22
  21. machineconfig/jobs/installer/python_scripts/sysabc.py +139 -0
  22. machineconfig/jobs/installer/{custom_dev → python_scripts}/wezterm.py +2 -19
  23. machineconfig/jobs/installer/{custom_dev → python_scripts}/winget.py +10 -14
  24. machineconfig/jobs/installer/python_scripts/yazi.py +121 -0
  25. machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_nfs +0 -1
  26. machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +13 -0
  27. machineconfig/jobs/scripts/powershell_scripts/obs.ps1 +4 -0
  28. machineconfig/jobs/scripts_dynamic/a.py +25 -0
  29. machineconfig/logger.py +0 -1
  30. machineconfig/profile/create_helper.py +21 -22
  31. machineconfig/profile/create_links_export.py +25 -11
  32. machineconfig/profile/create_shell_profile.py +14 -3
  33. machineconfig/profile/mapper.toml +8 -6
  34. machineconfig/scripts/__init__.py +0 -4
  35. machineconfig/scripts/linux/wrap_mcfg +20 -21
  36. machineconfig/scripts/python/agents.py +74 -50
  37. machineconfig/scripts/python/ai/initai.py +1 -1
  38. machineconfig/scripts/python/ai/scripts/command_runner.ps1 +33 -0
  39. machineconfig/scripts/python/ai/{command_runner → scripts}/command_runner.sh +1 -1
  40. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +1 -1
  41. machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Thinking-Beast-Mode.chatmode.md → agents/Thinking-Beast-Mode.agent.md} +0 -1
  42. machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md → agents/Ultimate-Transparent-Thinking-Beast-Mode.agent.md} +0 -1
  43. machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/deepResearch.chatmode.md → agents/deepResearch.agent.md} +2 -2
  44. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +5 -5
  45. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +4 -0
  46. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/watch_exec.prompt.md +20 -0
  47. machineconfig/scripts/python/ai/solutions/generic.py +1 -1
  48. machineconfig/scripts/python/ai/{generate_files.py → utils/generate_files.py} +2 -2
  49. machineconfig/scripts/python/cloud.py +6 -6
  50. machineconfig/scripts/python/croshell.py +67 -60
  51. machineconfig/scripts/python/devops.py +41 -21
  52. machineconfig/scripts/python/devops_navigator.py +0 -4
  53. machineconfig/scripts/python/env_manager/env_manager_tui.py +204 -0
  54. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  55. machineconfig/scripts/python/fire_jobs.py +95 -67
  56. machineconfig/scripts/python/ftpx.py +44 -17
  57. machineconfig/scripts/python/helpers/ast_search.py +74 -0
  58. machineconfig/scripts/python/helpers/qr_code.py +166 -0
  59. machineconfig/scripts/python/helpers/repo_rag.py +325 -0
  60. machineconfig/scripts/python/helpers/symantic_search.py +25 -0
  61. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.json +1 -1
  62. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.py +9 -7
  63. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_gemini.py +21 -8
  64. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +0 -12
  65. machineconfig/scripts/python/helpers_agents/fire_agents_help_launch.py +30 -11
  66. machineconfig/scripts/python/helpers_agents/fire_agents_helper_types.py +9 -2
  67. machineconfig/scripts/python/helpers_agents/privacy/configs/aichat/config.yaml +5 -0
  68. machineconfig/scripts/python/helpers_agents/privacy/configs/aider/.aider.conf.yml +2 -0
  69. machineconfig/scripts/python/helpers_agents/privacy/configs/copilot/config.yml +1 -0
  70. machineconfig/scripts/python/helpers_agents/privacy/configs/crush/crush.json +10 -0
  71. machineconfig/scripts/python/helpers_agents/privacy/configs/gemini/settings.json +12 -0
  72. machineconfig/scripts/python/helpers_agents/privacy/privacy.py +109 -0
  73. machineconfig/scripts/python/helpers_agents/templates/prompt.txt +8 -4
  74. machineconfig/scripts/python/helpers_agents/templates/template.sh +18 -8
  75. machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
  76. machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
  77. machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
  78. machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
  79. machineconfig/scripts/python/helpers_croshell/crosh.py +3 -3
  80. machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
  81. machineconfig/scripts/python/helpers_devops/cli_config.py +19 -25
  82. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
  83. machineconfig/scripts/python/helpers_devops/cli_nw.py +113 -26
  84. machineconfig/scripts/python/helpers_devops/cli_repos.py +37 -11
  85. machineconfig/scripts/python/helpers_devops/cli_self.py +95 -42
  86. machineconfig/scripts/python/helpers_devops/cli_share_file.py +9 -9
  87. machineconfig/scripts/python/helpers_devops/cli_share_server.py +13 -12
  88. machineconfig/scripts/python/helpers_devops/{cli_terminal.py → cli_share_terminal.py} +15 -17
  89. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  90. machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
  91. machineconfig/scripts/python/helpers_devops/run_script.py +180 -0
  92. machineconfig/scripts/python/helpers_devops/themes/choose_wezterm_theme.py +1 -1
  93. machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -19
  94. machineconfig/scripts/python/helpers_fire_command/fire_jobs_args_helper.py +1 -0
  95. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +25 -15
  96. machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +3 -3
  97. machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfg.ps1 +58 -1
  98. machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
  99. machineconfig/scripts/python/helpers_network/address.py +176 -0
  100. machineconfig/scripts/python/helpers_network/address_switch.py +78 -0
  101. machineconfig/scripts/python/{nw → helpers_network}/mount_nfs.py +2 -2
  102. machineconfig/scripts/python/{nw → helpers_network}/mount_ssh.py +1 -1
  103. machineconfig/scripts/python/{nw/devops_add_identity.py → helpers_network/ssh_add_identity.py} +35 -1
  104. machineconfig/scripts/python/{nw/devops_add_ssh_key.py → helpers_network/ssh_add_ssh_key.py} +26 -7
  105. machineconfig/scripts/python/{nw → helpers_network}/ssh_debug_linux.py +7 -7
  106. machineconfig/scripts/python/{nw → helpers_network}/ssh_debug_windows.py +4 -4
  107. machineconfig/scripts/python/helpers_repos/clone.py +0 -1
  108. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +13 -5
  109. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  110. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  111. machineconfig/scripts/python/helpers_repos/repo_analyzer_1.py +160 -0
  112. machineconfig/scripts/python/helpers_repos/{count_lines.py → repo_analyzer_2.py} +113 -192
  113. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +19 -13
  114. machineconfig/scripts/python/helpers_utils/download.py +150 -0
  115. machineconfig/scripts/python/helpers_utils/pdf.py +96 -0
  116. machineconfig/scripts/python/helpers_utils/python.py +187 -0
  117. machineconfig/scripts/python/interactive.py +30 -31
  118. machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -5
  119. machineconfig/scripts/python/msearch.py +57 -6
  120. machineconfig/scripts/python/sessions.py +100 -31
  121. machineconfig/scripts/python/terminal.py +26 -17
  122. machineconfig/scripts/python/utils.py +17 -15
  123. machineconfig/scripts/windows/wrap_mcfg.ps1 +6 -3
  124. machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
  125. machineconfig/settings/linters/.ruff.toml +1 -1
  126. machineconfig/settings/shells/bash/init.sh +29 -2
  127. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +1 -1
  128. machineconfig/settings/shells/nushell/config.nu +2 -2
  129. machineconfig/settings/shells/nushell/env.nu +45 -6
  130. machineconfig/settings/shells/nushell/init.nu +282 -95
  131. machineconfig/settings/shells/pwsh/init.ps1 +1 -0
  132. machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
  133. machineconfig/settings/shells/zsh/init.sh +1 -8
  134. machineconfig/settings/television/cable_unix/alias.toml +8 -0
  135. machineconfig/settings/television/cable_unix/aws-buckets.toml +14 -0
  136. machineconfig/settings/television/cable_unix/aws-instances.toml +13 -0
  137. machineconfig/settings/television/cable_unix/bash-history.toml +8 -0
  138. machineconfig/settings/television/cable_unix/channels.toml +19 -0
  139. machineconfig/settings/television/cable_unix/dirs.toml +13 -0
  140. machineconfig/settings/television/cable_unix/distrobox-list.toml +42 -0
  141. machineconfig/settings/television/cable_unix/docker-images.toml +13 -0
  142. machineconfig/settings/television/cable_unix/dotfiles.toml +11 -0
  143. machineconfig/settings/television/cable_unix/env.toml +17 -0
  144. machineconfig/settings/television/cable_unix/files.toml +11 -0
  145. machineconfig/settings/television/cable_unix/fish-history.toml +8 -0
  146. machineconfig/settings/television/cable_unix/git-branch.toml +11 -0
  147. machineconfig/settings/television/cable_unix/git-diff.toml +10 -0
  148. machineconfig/settings/television/cable_unix/git-log.toml +12 -0
  149. machineconfig/settings/television/cable_unix/git-reflog.toml +12 -0
  150. machineconfig/settings/television/cable_unix/git-repos.toml +16 -0
  151. machineconfig/settings/television/cable_unix/guix.toml +20 -0
  152. machineconfig/settings/television/cable_unix/just-recipes.toml +18 -0
  153. machineconfig/settings/television/cable_unix/k8s-deployments.toml +36 -0
  154. machineconfig/settings/television/cable_unix/k8s-pods.toml +50 -0
  155. machineconfig/settings/television/cable_unix/k8s-services.toml +36 -0
  156. machineconfig/settings/television/cable_unix/man-pages.toml +24 -0
  157. machineconfig/settings/television/cable_unix/nu-history.toml +7 -0
  158. machineconfig/settings/television/cable_unix/procs.toml +20 -0
  159. machineconfig/settings/television/cable_unix/text.toml +17 -0
  160. machineconfig/settings/television/cable_unix/tldr.toml +18 -0
  161. machineconfig/settings/television/cable_unix/zsh-history.toml +9 -0
  162. machineconfig/settings/television/cable_windows/alias.toml +7 -0
  163. machineconfig/settings/television/cable_windows/dirs.toml +13 -0
  164. machineconfig/settings/television/cable_windows/docker-images.toml +13 -0
  165. machineconfig/settings/television/cable_windows/dotfiles.toml +11 -0
  166. machineconfig/settings/television/cable_windows/env.toml +17 -0
  167. machineconfig/settings/television/cable_windows/files.toml +14 -0
  168. machineconfig/settings/television/cable_windows/git-branch.toml +11 -0
  169. machineconfig/settings/television/cable_windows/git-diff.toml +10 -0
  170. machineconfig/settings/television/cable_windows/git-log.toml +11 -0
  171. machineconfig/settings/television/cable_windows/git-reflog.toml +11 -0
  172. machineconfig/settings/television/cable_windows/git-repos.toml +15 -0
  173. machineconfig/settings/television/cable_windows/nu-history.toml +7 -0
  174. machineconfig/settings/television/cable_windows/pwsh-history.toml +6 -0
  175. machineconfig/settings/television/cable_windows/text.toml +17 -0
  176. machineconfig/settings/wt/__init__.py +0 -0
  177. machineconfig/settings/yazi/init.lua +49 -24
  178. machineconfig/settings/yazi/keymap_linux.toml +19 -4
  179. machineconfig/settings/yazi/keymap_windows.toml +0 -1
  180. machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
  181. machineconfig/settings/yazi/theme.toml +4 -0
  182. machineconfig/settings/yazi/yazi_linux.toml +84 -0
  183. machineconfig/settings/yazi/yazi_windows.toml +58 -0
  184. machineconfig/settings/zellij/layouts/st.kdl +39 -8
  185. machineconfig/setup_linux/__init__.py +1 -2
  186. machineconfig/setup_linux/apps_desktop.sh +8 -27
  187. machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -10
  188. machineconfig/setup_linux/web_shortcuts/live_from_github.sh +31 -0
  189. machineconfig/setup_mac/__init__.py +2 -3
  190. machineconfig/setup_windows/__init__.py +3 -5
  191. machineconfig/setup_windows/ssh/openssh-server.ps1 +1 -1
  192. machineconfig/setup_windows/uv.ps1 +8 -1
  193. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -10
  194. machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +30 -0
  195. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +17 -0
  196. machineconfig/utils/accessories.py +7 -4
  197. machineconfig/utils/code.py +69 -27
  198. machineconfig/utils/files/headers.py +2 -2
  199. machineconfig/utils/installer_utils/github_release_bulk.py +156 -119
  200. machineconfig/utils/installer_utils/install_from_url.py +183 -0
  201. machineconfig/utils/installer_utils/installer_class.py +43 -100
  202. machineconfig/utils/installer_utils/installer_cli.py +175 -0
  203. machineconfig/utils/installer_utils/installer_helper.py +129 -0
  204. machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +36 -85
  205. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +16 -59
  206. machineconfig/utils/io.py +0 -1
  207. machineconfig/utils/links.py +2 -2
  208. machineconfig/utils/meta.py +30 -16
  209. machineconfig/utils/options.py +42 -24
  210. machineconfig/utils/options_tv.py +119 -0
  211. machineconfig/utils/path_extended.py +42 -20
  212. machineconfig/utils/path_helper.py +75 -22
  213. machineconfig/utils/procs.py +1 -1
  214. machineconfig/utils/scheduler.py +20 -53
  215. machineconfig/utils/schemas/layouts/layout_types.py +1 -1
  216. machineconfig/utils/ssh.py +159 -418
  217. machineconfig/utils/ssh_utils/abc.py +5 -0
  218. machineconfig/utils/ssh_utils/copy_from_here.py +111 -0
  219. machineconfig/utils/ssh_utils/copy_to_here.py +303 -0
  220. machineconfig/utils/ssh_utils/utils.py +142 -0
  221. machineconfig/utils/ssh_utils/wsl.py +210 -0
  222. machineconfig/utils/terminal.py +1 -0
  223. machineconfig/utils/upgrade_packages.py +6 -1
  224. machineconfig/utils/ve.py +12 -4
  225. machineconfig-8.14.dist-info/METADATA +132 -0
  226. {machineconfig-7.50.dist-info → machineconfig-8.14.dist-info}/RECORD +264 -215
  227. {machineconfig-7.50.dist-info → machineconfig-8.14.dist-info}/entry_points.txt +2 -4
  228. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  229. machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
  230. machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
  231. machineconfig/jobs/installer/powershell_scripts/openssh-server_add_key.ps1 +0 -7
  232. machineconfig/jobs/installer/powershell_scripts/openssh-server_copy-ssh-id.ps1 +0 -14
  233. machineconfig/scripts/linux/other/switch_ip +0 -20
  234. machineconfig/scripts/python/ai/command_runner/prompt.txt +0 -9
  235. machineconfig/scripts/python/define.py +0 -31
  236. machineconfig/scripts/python/explore.py +0 -49
  237. machineconfig/scripts/python/helpers_devops/cli_utils.py +0 -246
  238. machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfag +0 -17
  239. machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfrga +0 -21
  240. machineconfig/scripts/python/helpers_msearch/scripts_linux/skrg +0 -4
  241. machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfb.ps1 +0 -3
  242. machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfrga.bat +0 -20
  243. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +0 -17
  244. machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
  245. machineconfig/scripts/python/nw/wsl_windows_transfer.py +0 -66
  246. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +0 -13
  247. machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
  248. machineconfig/settings/lf/windows/fzf_edit.ps1 +0 -6
  249. machineconfig/settings/lf/windows/tst.ps1 +0 -1
  250. machineconfig/settings/yazi/yazi.toml +0 -17
  251. machineconfig/setup_linux/apps.sh +0 -66
  252. machineconfig/setup_linux/others/cli_installation.sh +0 -137
  253. machineconfig/setup_linux/others/mint_keyboard_shortcuts.sh +0 -30
  254. machineconfig/setup_linux/ssh/openssh_all.sh +0 -25
  255. machineconfig/setup_linux/ssh/openssh_wsl.sh +0 -38
  256. machineconfig/setup_mac/apps.sh +0 -73
  257. machineconfig/setup_windows/apps.ps1 +0 -62
  258. machineconfig/setup_windows/others/obs.ps1 +0 -4
  259. machineconfig/setup_windows/ssh/add_identity.ps1 +0 -11
  260. machineconfig/utils/installer_utils/installer.py +0 -221
  261. machineconfig-7.50.dist-info/METADATA +0 -92
  262. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  263. /machineconfig/jobs/installer/{custom_dev → python_scripts}/__init__.py +0 -0
  264. /machineconfig/jobs/installer/{custom_dev → python_scripts}/alacritty.py +0 -0
  265. /machineconfig/jobs/installer/{custom_dev → python_scripts}/bypass_paywall.py +0 -0
  266. /machineconfig/jobs/installer/{custom_dev → python_scripts}/cursor.py +0 -0
  267. /machineconfig/jobs/installer/{custom_dev → python_scripts}/espanso.py +0 -0
  268. /machineconfig/jobs/installer/{custom → python_scripts}/gh.py +0 -0
  269. /machineconfig/jobs/installer/{custom_dev → python_scripts}/goes.py +0 -0
  270. /machineconfig/jobs/installer/{custom_dev → python_scripts}/lvim.py +0 -0
  271. /machineconfig/jobs/installer/{custom_dev → python_scripts}/redis.py +0 -0
  272. /machineconfig/{setup_linux/others → jobs/scripts/bash_scripts}/android.sh +0 -0
  273. /machineconfig/jobs/{installer/linux_scripts → scripts/bash_scripts}/lid.sh +0 -0
  274. /machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_drive +0 -0
  275. /machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_nw_drive +0 -0
  276. /machineconfig/{scripts/python/nw → jobs/scripts/bash_scripts}/mount_smb +0 -0
  277. /machineconfig/{scripts/linux/other → jobs/scripts/bash_scripts}/share_cloud.sh +0 -0
  278. /machineconfig/{scripts/linux/other → jobs/scripts/bash_scripts}/share_nfs +0 -0
  279. /machineconfig/{scripts/linux/other → jobs/scripts/bash_scripts}/start_docker +0 -0
  280. /machineconfig/{scripts → jobs/scripts/powershell_scripts}/Restore-ThunderbirdProfile.ps1 +0 -0
  281. /machineconfig/{setup_windows/others → jobs/scripts/powershell_scripts}/docker.ps1 +0 -0
  282. /machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/mount_nfs.ps1 +0 -0
  283. /machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/mount_nw.ps1 +0 -0
  284. /machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/mount_smb.ps1 +0 -0
  285. /machineconfig/{setup_windows/others → jobs/scripts/powershell_scripts}/power_options.ps1 +0 -0
  286. /machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/share_cloud.cmd +0 -0
  287. /machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/share_smb.ps1 +0 -0
  288. /machineconfig/{scripts/windows/mounts → jobs/scripts/powershell_scripts}/unlock_bitlocker.ps1 +0 -0
  289. /machineconfig/scripts/python/{nw → ai/utils}/__init__.py +0 -0
  290. /machineconfig/scripts/python/ai/{vscode_tasks.py → utils/vscode_tasks.py} +0 -0
  291. /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
  292. /machineconfig/{setup_windows/wt_and_pwsh → scripts/python/helpers_network}/__init__.py +0 -0
  293. /machineconfig/scripts/python/{nw → helpers_network}/mount_nw_drive.py +0 -0
  294. /machineconfig/scripts/python/{nw → helpers_network}/onetimeshare.py +0 -0
  295. /machineconfig/scripts/python/{nw → helpers_network}/wifi_conn.py +0 -0
  296. /machineconfig/{setup_windows/wt_and_pwsh → settings/wt}/set_wt_settings.py +0 -0
  297. {machineconfig-7.50.dist-info → machineconfig-8.14.dist-info}/WHEEL +0 -0
  298. {machineconfig-7.50.dist-info → machineconfig-8.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,129 @@
1
+ from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
2
+ from machineconfig.utils.schemas.installer.installer_types import InstallerData
3
+ from pathlib import Path
4
+ from machineconfig.utils.path_extended import DECOMPRESS_SUPPORTED_FORMATS, PathExtended
5
+ from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR
6
+
7
+
8
+ def get_group_name_to_repr() -> dict[str, str]:
9
+ # Build category options and maintain a mapping from display text to actual category name
10
+ category_display_to_name: dict[str, str] = {}
11
+ for group_name, group_values in PACKAGE_GROUP2NAMES.items():
12
+ display = f"📦 {group_name:<20}" + " -- " + f"{'|'.join(group_values):<60}"
13
+ category_display_to_name[display] = group_name
14
+ return category_display_to_name
15
+
16
+
17
+ def handle_installer_not_found(search_term: str, app_apps: list[InstallerData]) -> None: # type: ignore
18
+ """Handle installer not found with friendly suggestions using fuzzy matching."""
19
+ from difflib import get_close_matches
20
+ from rich.console import Console
21
+ from rich.panel import Panel
22
+ from rich.table import Table
23
+ all_names = sorted([inst["appName"] for inst in app_apps])
24
+ name_to_doc = {inst["appName"]: inst["doc"] for inst in app_apps}
25
+ all_descriptions = {f"{inst['appName']}: {inst['doc']}": inst["appName"] for inst in app_apps}
26
+
27
+ close_name_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
28
+ close_description_matches = get_close_matches(search_term, list(all_descriptions.keys()), n=5, cutoff=0.4)
29
+
30
+ search_lower = search_term.lower()
31
+ substring_matches = [
32
+ inst["appName"]
33
+ for inst in app_apps
34
+ if search_lower in inst["appName"].lower() or search_lower in inst["doc"].lower()
35
+ ]
36
+
37
+ ordered_matches: list[str] = list(
38
+ dict.fromkeys(
39
+ close_name_matches
40
+ + [all_descriptions[desc] for desc in close_description_matches]
41
+ + substring_matches
42
+ )
43
+ )
44
+ top_matches = ordered_matches[:10]
45
+ console = Console()
46
+
47
+ console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
48
+ if top_matches:
49
+ console.print("🤔 Did you mean one of these?", style="yellow")
50
+ table = Table(show_header=True, header_style="bold", box=None, pad_edge=False)
51
+ table.add_column("#", justify="right", width=3)
52
+ table.add_column("Installer", style="green")
53
+ table.add_column("Description", style="dim", overflow="fold")
54
+ for i, match in enumerate(top_matches, 1):
55
+ table.add_row(f"[cyan]{i}[/cyan]", match, name_to_doc.get(match, ""))
56
+ console.print(table)
57
+ else:
58
+ console.print("📋 Here are some available options:", style="blue")
59
+ # Show first 10 installers as examples
60
+ if len(all_names) > 10:
61
+ sample_names = all_names[:10]
62
+ else:
63
+ sample_names = all_names
64
+ table = Table(show_header=True, header_style="bold", box=None, pad_edge=False)
65
+ table.add_column("#", justify="right", width=3)
66
+ table.add_column("Installer", style="green")
67
+ table.add_column("Description", style="dim", overflow="fold")
68
+ for i, name in enumerate(sample_names, 1):
69
+ table.add_row(f"[cyan]{i}[/cyan]", name, name_to_doc.get(name, ""))
70
+ console.print(table)
71
+ if len(all_names) > 10:
72
+ console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
73
+
74
+ panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(PACKAGE_GROUP2NAMES.keys())}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
75
+ console.print(panel)
76
+
77
+
78
+ def install_deb_package(downloaded: Path) -> None:
79
+ from rich import print as rprint
80
+ from rich.panel import Panel
81
+ print(f"📦 Installing .deb package: {downloaded}")
82
+ import platform
83
+ import subprocess
84
+ assert platform.system() == "Linux"
85
+ result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
86
+ success = result.returncode == 0 and result.stderr == ""
87
+ if not success:
88
+ from rich.console import Group
89
+ desc = "Installing .deb"
90
+ sub_panels = []
91
+ if result.stdout:
92
+ sub_panels.append(Panel(result.stdout, title="STDOUT", style="blue"))
93
+ if result.stderr:
94
+ sub_panels.append(Panel(result.stderr, title="STDERR", style="red"))
95
+ group_content = Group(f"❌ {desc} failed\nReturn code: {result.returncode}", *sub_panels)
96
+ rprint(Panel(group_content, title=desc, style="red"))
97
+ print("🗑️ Cleaning up .deb package...")
98
+ if downloaded.is_file():
99
+ downloaded.unlink(missing_ok=True)
100
+ elif downloaded.is_dir():
101
+ import shutil
102
+ shutil.rmtree(downloaded, ignore_errors=True)
103
+
104
+
105
+ def download_and_prepare(download_url: str) -> PathExtended:
106
+ # archive_path = PathExtended(download_url).download(folder=INSTALL_TMP_DIR)
107
+ from machineconfig.scripts.python.helpers_utils.download import download
108
+ downloaded_object = download(download_url, output_dir=str(INSTALL_TMP_DIR))
109
+ if downloaded_object is None:
110
+ raise ValueError(f"Failed to download from URL: {download_url}")
111
+ archive_path = PathExtended(downloaded_object)
112
+ extracted_path = archive_path
113
+ if extracted_path.is_file() and any(ext in archive_path.suffixes for ext in DECOMPRESS_SUPPORTED_FORMATS):
114
+ extracted_path = archive_path.decompress()
115
+ # print(f"Decompressed {archive_path} to {extracted_path}")
116
+ archive_path.delete(sure=True)
117
+ if extracted_path.is_dir():
118
+ nested_items = list(extracted_path.glob("*"))
119
+ if len(nested_items) == 1:
120
+ nested_path = PathExtended(nested_items[0])
121
+ if nested_path.is_file() and any(ex in nested_path.suffixes for ex in DECOMPRESS_SUPPORTED_FORMATS):
122
+ extracted_path = nested_path.decompress()
123
+ nested_path.delete(sure=True)
124
+ elif extracted_path.is_dir() and len(extracted_path.search("*", r=True)) == 1:
125
+ only_file_in = next(extracted_path.glob("*"))
126
+ if only_file_in.is_file() and any(ext in str(only_file_in) for ext in DECOMPRESS_SUPPORTED_FORMATS): # further decompress
127
+ extracted_path = only_file_in.decompress()
128
+ only_file_in.delete(sure=True)
129
+ return extracted_path
@@ -7,23 +7,28 @@ import subprocess
7
7
  import platform
8
8
 
9
9
 
10
- def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optional[str] = None, delete: bool = True, rename_to: Optional[str] = None):
11
- print("🔍 PROCESSING WINDOWS EXECUTABLE 🔍")
12
- if exe_name is not None and ".exe" in exe_name:
13
- exe_name = exe_name.replace(".exe", "")
10
+ def find_move_delete_windows(downloaded_file_path: PathExtended, tool_name: Optional[str], delete: bool, rename_to: Optional[str]):
11
+ # print("🔍 PROCESSING WINDOWS EXECUTABLE 🔍")
12
+ # if exe_name is not None and len(exe_name.split("+")) > 1:
13
+ # last_result = None
14
+ # for a_binary in [x.strip() for x in exe_name.split("+") if x.strip() != ""]:
15
+ # last_result = find_move_delete_windows(downloaded_file_path=downloaded_file_path, exe_name=a_binary, delete=delete, rename_to=rename_to)
16
+ # return last_result
17
+ if tool_name is not None and ".exe" in tool_name:
18
+ tool_name = tool_name.replace(".exe", "")
14
19
  if downloaded_file_path.is_file():
15
20
  exe = downloaded_file_path
16
21
  print(f"📄 Found direct executable file: {exe}")
17
22
  else:
18
23
  print(f"🔎 Searching for executable in: {downloaded_file_path}")
19
- if exe_name is None:
24
+ if tool_name is None:
20
25
  exe = downloaded_file_path.search("*.exe", r=True)[0]
21
26
  print(f"✅ Found executable: {exe}")
22
27
  else:
23
- tmp = downloaded_file_path.search(f"{exe_name}.exe", r=True)
28
+ tmp = downloaded_file_path.search(f"{tool_name}.exe", r=True)
24
29
  if len(tmp) == 1:
25
30
  exe = tmp[0]
26
- print(f"✅ Found exact match for {exe_name}.exe: {exe}")
31
+ print(f"✅ Found exact match for {tool_name}.exe: {exe}")
27
32
  else:
28
33
  search_res = downloaded_file_path.search("*.exe", r=True)
29
34
  if len(search_res) == 0:
@@ -52,7 +57,13 @@ def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optio
52
57
  return exe_new_location
53
58
 
54
59
 
55
- def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Optional[bool] = True, rename_to: Optional[str] = None):
60
+ def find_move_delete_linux(downloaded: PathExtended, tool_name: Optional[str], delete: bool, rename_to: Optional[str]):
61
+ # if len(tool_name.split("+")) > 1:
62
+ # last_result = None
63
+ # for a_binary in [x.strip() for x in tool_name.split("+") if x.strip() != ""]:
64
+ # last_result = find_move_delete_linux(downloaded=downloaded, tool_name=a_binary, delete=False, rename_to=rename_to)
65
+ # return last_result
66
+
56
67
  print("🔍 PROCESSING LINUX EXECUTABLE 🔍")
57
68
  if downloaded.is_file():
58
69
  exe = downloaded
@@ -64,16 +75,24 @@ def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Opt
64
75
  exe = res[0]
65
76
  print(f"✅ Found match for pattern '*{tool_name}*': {exe}")
66
77
  else:
67
- exe_search_res = downloaded.search(tool_name, folders=False, r=True)
68
- if len(exe_search_res) == 0:
69
- print(f"❌ ERROR: No search results for `{tool_name}` in `{downloaded}`")
70
- raise IndexError(f"No executable found in {downloaded}")
71
- elif len(exe_search_res) == 1:
72
- exe = exe_search_res[0]
73
- print(f"✅ Found exact match for '{tool_name}': {exe}")
74
- else:
75
- exe = max(exe_search_res, key=lambda x: x.size("kb"))
78
+ if tool_name is None: # no tool name provided, get the largest executable
79
+ search_res = downloaded.search("*", folders=False, files=True, r=True)
80
+ if len(search_res) == 0:
81
+ print(f"❌ ERROR: No search results in `{downloaded}`")
82
+ raise IndexError(f"No executable found in {downloaded}")
83
+ exe = max(search_res, key=lambda x: x.size("kb"))
76
84
  print(f"✅ Selected largest executable ({exe.size('kb')} KB): {exe}")
85
+ else:
86
+ exe_search_res = downloaded.search(tool_name, folders=False, r=True)
87
+ if len(exe_search_res) == 0:
88
+ print(f"❌ ERROR: No search results for `{tool_name}` in `{downloaded}`")
89
+ raise IndexError(f"No executable found in {downloaded}")
90
+ elif len(exe_search_res) == 1:
91
+ exe = exe_search_res[0]
92
+ print(f"✅ Found exact match for '{tool_name}': {exe}")
93
+ else:
94
+ exe = max(exe_search_res, key=lambda x: x.size("kb"))
95
+ print(f"✅ Selected largest executable ({exe.size('kb')} KB): {exe}")
77
96
 
78
97
  if rename_to and exe.name != rename_to:
79
98
  print(f"🏷️ Renaming '{exe.name}' to '{rename_to}'")
@@ -191,71 +210,3 @@ def check_if_installed_already(exe_name: str, version: Optional[str], use_cache:
191
210
  return ("⚠️ NotInstalled", "None", version or "unknown")
192
211
 
193
212
 
194
- def parse_apps_installer_linux(txt: str) -> dict[str, tuple[str, str]]:
195
- """Parse Linux shell installation scripts into logical chunks.
196
-
197
- Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
198
- mapping block names to (description, shell script content) tuples.
199
-
200
- Returns:
201
- dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
202
- """
203
- chunks = txt.split('# --GROUP:')
204
- res: dict[str, tuple[str, str]] = {}
205
-
206
- for chunk in chunks[1:]: # Skip first empty chunk before first group
207
- lines = chunk.split('\n')
208
- # First line contains the group name and description in format "NAME:DESCRIPTION"
209
- group_line = lines[0].strip()
210
-
211
- # Extract group name and description
212
- if ':' in group_line:
213
- parts = group_line.split(':', 1) # Split only on first colon
214
- group_name = parts[0].strip()
215
- group_description = parts[1].strip() if len(parts) > 1 else ""
216
- else:
217
- group_name = group_line
218
- group_description = ""
219
-
220
- # Rest is the content
221
- content = '\n'.join(lines[1:]).strip()
222
-
223
- if group_name and content:
224
- res[group_name] = (group_description, content)
225
-
226
- return res
227
-
228
-
229
- def parse_apps_installer_windows(txt: str) -> dict[str, tuple[str, str]]:
230
- """Parse Windows PowerShell installation scripts into logical chunks.
231
-
232
- Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
233
- mapping block names to (description, PowerShell script content) tuples.
234
-
235
- Returns:
236
- dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
237
- """
238
- chunks = txt.split('# --GROUP:')
239
- res: dict[str, tuple[str, str]] = {}
240
-
241
- for chunk in chunks[1:]: # Skip first chunk before first group
242
- lines = chunk.split('\n')
243
- # First line contains the group name and description in format "NAME:DESCRIPTION"
244
- group_line = lines[0].strip()
245
-
246
- # Extract group name and description
247
- if ':' in group_line:
248
- parts = group_line.split(':', 1) # Split only on first colon
249
- group_name = parts[0].strip()
250
- group_description = parts[1].strip() if len(parts) > 1 else ""
251
- else:
252
- group_name = group_line
253
- group_description = ""
254
-
255
- # Rest is the content
256
- content = '\n'.join(lines[1:]).strip()
257
-
258
- if group_name and content:
259
- res[group_name] = (group_description, content)
260
-
261
- return res
@@ -1,9 +1,9 @@
1
1
  """package manager"""
2
2
 
3
- from machineconfig.utils.installer_utils.installer_abc import check_if_installed_already, parse_apps_installer_linux, parse_apps_installer_windows
3
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_if_installed_already
4
4
  from machineconfig.utils.installer_utils.installer_class import Installer
5
5
  from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
6
- from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
6
+ from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
7
7
  from machineconfig.utils.path_extended import PathExtended
8
8
  from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT, LINUX_INSTALL_PATH
9
9
  from machineconfig.utils.io import read_json
@@ -18,7 +18,7 @@ from joblib import Parallel, delayed
18
18
  def check_latest():
19
19
  console = Console() # Added console initialization
20
20
  console.print(Panel("🔍 CHECKING FOR LATEST VERSIONS", title="Status", expand=False)) # Replaced print with Panel
21
- installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["ESSENTIAL"])
21
+ installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["termabc"])
22
22
  installers_github = []
23
23
  for inst__ in installers:
24
24
  app_name = inst__["appName"]
@@ -91,8 +91,12 @@ def get_installed_cli_apps():
91
91
  return apps
92
92
 
93
93
 
94
- def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: Optional[list[PACKAGE_GROUPS]]) -> list[InstallerData]:
95
- res_all = get_all_installer_data_files()
94
+ def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: Optional[list[str]]) -> list[InstallerData]:
95
+ import machineconfig.jobs.installer as module
96
+ from pathlib import Path
97
+ res_raw: InstallerDataFiles = read_json(Path(module.__file__).parent.joinpath("installer_data.json"))
98
+ res_all: list[InstallerData] = res_raw["installers"]
99
+
96
100
  acceptable_apps_names: list[str] | None = None
97
101
  if which_cats is not None:
98
102
  acceptable_apps_names = []
@@ -105,40 +109,17 @@ def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: O
105
109
  if acceptable_apps_names is not None:
106
110
  if installer_data["appName"] not in acceptable_apps_names:
107
111
  continue
108
- if installer_data["fileNamePattern"][arch][os] is None:
109
- continue
112
+ try:
113
+ if installer_data["fileNamePattern"][arch][os] is None:
114
+ continue
115
+ except KeyError as ke:
116
+ print(f"❌ ERROR: Missing key in installer data: {ke}")
117
+ print(f"Installer data: {installer_data}")
118
+ raise KeyError(f"Missing key in installer data: {ke}")
110
119
  all_installers.append(installer_data)
111
120
  return all_installers
112
121
 
113
122
 
114
- def get_all_installer_data_files() -> list[InstallerData]:
115
- import machineconfig.jobs.installer as module
116
- from pathlib import Path
117
- res_raw: InstallerDataFiles = read_json(Path(module.__file__).parent.joinpath("installer_data.json"))
118
- res_final: list[InstallerData] = res_raw["installers"]
119
- return res_final
120
-
121
-
122
- def dynamically_extract_installers_system_groups_from_scripts():
123
- res_final: list[InstallerData] = []
124
- from platform import system
125
- if system() == "Windows":
126
- from machineconfig.setup_windows import APPS
127
- options_system = parse_apps_installer_windows(APPS.read_text(encoding="utf-8"))
128
- elif system() == "Linux":
129
- from machineconfig.setup_linux import APPS
130
- options_system = parse_apps_installer_linux(APPS.read_text(encoding="utf-8"))
131
- elif system() == "Darwin":
132
- from machineconfig.setup_mac import APPS
133
- options_system = parse_apps_installer_linux(APPS.read_text(encoding="utf-8"))
134
- else:
135
- raise NotImplementedError(f"❌ System {system()} not supported")
136
- os_name = get_os_name()
137
- for group_name, (docs, script) in options_system.items():
138
- item: InstallerData = {"appName": group_name, "doc": docs, "repoURL": "CMD", "fileNamePattern": {"amd64": {os_name: script}, "arm64": {os_name: script}}}
139
- res_final.append(item)
140
- return res_final
141
-
142
123
 
143
124
  def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs: int = 10, fresh: bool = False):
144
125
  print("🚀 BULK INSTALLATION PROCESS 🚀")
@@ -148,30 +129,6 @@ def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs:
148
129
  print("✅ Version cache cleared")
149
130
  if safe:
150
131
  pass
151
- # print("⚠️ Safe installation mode activated...")
152
- # from machineconfig.jobs.python.check_installations import APP_SUMMARY_PATH
153
- # if platform.system().lower() == "windows":
154
- # print("🪟 Moving applications to Windows Apps folder...")
155
- # # PathExtended.get_env().WindowsPaths().WindowsApps)
156
- # folder = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps")
157
- # apps_dir.search("*").apply(lambda app: app.move(folder=folder))
158
- # elif platform.system().lower() in ["linux", "darwin"]:
159
- # system_name = "Linux" if platform.system().lower() == "linux" else "macOS"
160
- # print(f"🐧 Moving applications to {system_name} bin folder...")
161
- # if platform.system().lower() == "linux":
162
- # install_path = LINUX_INSTALL_PATH
163
- # else: # Darwin/macOS
164
- # install_path = "/usr/local/bin"
165
- # Terminal().run(f"sudo mv {apps_dir.as_posix()}/* {install_path}/").capture().print_if_unsuccessful(desc=f"MOVING executable to {install_path}", strict_err=True, strict_returncode=True)
166
- # else:
167
- # error_msg = f"❌ ERROR: System {platform.system()} not supported"
168
- # print(error_msg)
169
- # raise NotImplementedError(error_msg)
170
-
171
- # apps_dir.delete(sure=True)
172
- # print(f"✅ Safe installation completed\n{'='*80}")
173
- # return None
174
-
175
132
  print(f"🚀 Starting installation of {len(installers_data)} packages...")
176
133
  print("📦 INSTALLING FIRST PACKAGE 📦")
177
134
  Installer(installers_data[0]).install(version=None)
machineconfig/utils/io.py CHANGED
@@ -1,4 +1,3 @@
1
- from __future__ import annotations
2
1
 
3
2
  from typing import Any, Union, Optional, Mapping
4
3
  from pathlib import Path
@@ -164,7 +164,7 @@ def symlink_map(config_file_default_path: PathExtended, self_managed_config_file
164
164
  else:
165
165
  # Files are different, use on_conflict strategy
166
166
  import subprocess
167
- command = f"""delta --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
167
+ command = f"""delta --paging never --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
168
168
  try:
169
169
  console.print(Panel(f"🆘 CONFLICT DETECTED | Showing diff between {config_file_default_path} and {self_managed_config_file_path}", title="Conflict Detected", expand=False))
170
170
  subprocess.run(command, shell=True, check=True)
@@ -293,7 +293,7 @@ def copy_map(config_file_default_path: PathExtended, self_managed_config_file_pa
293
293
  else:
294
294
  # Files are different, use on_conflict strategy
295
295
  import subprocess
296
- command = f"""delta --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
296
+ command = f"""delta --paging never --side-by-side "{config_file_default_path}" "{self_managed_config_file_path}" """
297
297
  try:
298
298
  console.print(Panel(f"🆘 CONFLICT DETECTED | Showing diff between {config_file_default_path} and {self_managed_config_file_path}", title="Conflict Detected", expand=False))
299
299
  subprocess.run(command, shell=True, check=True)
@@ -29,7 +29,8 @@ except (ImportError, ModuleNotFoundError) as ex:
29
29
  return txt
30
30
 
31
31
 
32
- def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_module: bool) -> str:
32
+ def lambda_to_python_script(lmb: Callable[[], Any],
33
+ in_global: bool, import_module: bool) -> str:
33
34
  """
34
35
  caveats: always use keyword arguments in the lambda call for best results.
35
36
  return statement not allowed in the wrapped function (otherwise it can be put in the global space)
@@ -55,6 +56,16 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
55
56
  import types as _types
56
57
  from pathlib import Path as _Path
57
58
 
59
+ def _stringify_annotation(annotation: Any) -> Any:
60
+ if annotation is _inspect.Signature.empty or annotation is _inspect.Parameter.empty:
61
+ return annotation
62
+ if isinstance(annotation, str):
63
+ return annotation
64
+ try:
65
+ return _inspect.formatannotation(annotation)
66
+ except Exception:
67
+ return str(annotation)
68
+
58
69
  # sanity checks
59
70
  if not (callable(lmb) and isinstance(lmb, _types.LambdaType)):
60
71
  raise TypeError("Expected a lambda function object")
@@ -174,16 +185,18 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
174
185
  else:
175
186
  new_default = param.default
176
187
 
177
- # Recreate the Parameter (keeping annotation and kind)
188
+ normalized_annotation = _stringify_annotation(param.annotation)
189
+
178
190
  if new_default is _inspect.Parameter.empty:
179
- new_param = _inspect.Parameter(name, param.kind, annotation=param.annotation)
191
+ new_param = _inspect.Parameter(name, param.kind, annotation=normalized_annotation)
180
192
  else:
181
193
  new_param = _inspect.Parameter(
182
- name, param.kind, default=new_default, annotation=param.annotation
194
+ name, param.kind, default=new_default, annotation=normalized_annotation
183
195
  )
184
196
  new_params.append(new_param)
185
197
 
186
- new_sig = _inspect.Signature(parameters=new_params, return_annotation=sig.return_annotation)
198
+ return_annotation = _stringify_annotation(sig.return_annotation)
199
+ new_sig = _inspect.Signature(parameters=new_params, return_annotation=return_annotation)
187
200
 
188
201
  # If in_global mode, return kwargs as global assignments + dedented body
189
202
  if in_global:
@@ -200,15 +213,11 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
200
213
 
201
214
  # Build type annotation string if available
202
215
  if param.annotation is not _inspect.Parameter.empty:
203
- # Try to get a nice string representation of the annotation
204
- try:
205
- if hasattr(param.annotation, "__name__"):
206
- type_str = param.annotation.__name__
207
- else:
208
- type_str = str(param.annotation)
209
- except Exception:
210
- type_str = str(param.annotation)
211
- global_assignments.append(f"{name}: {type_str} = {repr(value)}")
216
+ annotation_literal = _stringify_annotation(param.annotation)
217
+ if isinstance(annotation_literal, str):
218
+ global_assignments.append(f"{name}: {repr(annotation_literal)} = {repr(value)}")
219
+ else:
220
+ global_assignments.append(f"{name} = {repr(value)}")
212
221
  else:
213
222
  global_assignments.append(f"{name} = {repr(value)}")
214
223
 
@@ -233,10 +242,15 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
233
242
 
234
243
  if "Optional" in result_text or "Any" in result_text or "Union" in result_text or "Literal" in result_text:
235
244
  result_text = "from typing import Optional, Any, Union, Literal\n\n" + result_text
236
-
237
245
  if import_prefix:
238
246
  result_text = f"{import_prefix}{result_text}"
239
247
  return result_text
240
248
 
241
249
  if __name__ == "__main__":
242
- pass
250
+ from machineconfig.utils.code import print_code
251
+ import_code_robust = "<import_code_robust>"
252
+ res = lambda_to_python_script(
253
+ lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
254
+ in_global=True, import_module=False
255
+ )
256
+ print(res)
@@ -1,36 +1,54 @@
1
+
1
2
  from pathlib import Path
2
- from machineconfig.utils.installer_utils.installer_abc 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
7
  from typing import Optional, Union, Iterable, overload, Literal, cast
8
8
 
9
-
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)
14
-
15
-
16
9
  @overload
17
- def choose_from_options[T](msg: str, options: Iterable[T], multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
10
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False) -> T: ...
18
11
  @overload
19
- def choose_from_options[T](msg: str, options: Iterable[T], multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> list[T]: ...
20
- def choose_from_options[T](msg: str, options: Iterable[T], multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> Union[T, list[T]]:
12
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, ) -> list[T]: ...
13
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, ) -> Union[T, list[T]]:
21
14
  # TODO: replace with https://github.com/tmbo/questionary
22
15
  # # also see https://github.com/charmbracelet/gum
23
16
  options_strings: list[str] = [str(x) for x in options]
24
17
  default_string = str(default) if default is not None else None
25
18
  console = Console()
26
- fzf_exists = check_tool_exists("fzf")
27
- # print("\n" * 10, f"{fzf=}, {fzf_exists=}", "\n" * 10)
28
- if fzf and fzf_exists:
29
- from pyfzf.pyfzf import FzfPrompt
30
- fzf_prompt = FzfPrompt()
31
- nl = "\n"
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, ' ')}")
33
- # --border=rounded doens't work on older versions of fzf installed at Ubuntu 20.04
19
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
20
+ # from machineconfig.utils.installer_utils.installer_cli import check_tool_exists
21
+ # print("ch1")
22
+ if tv and check_tool_exists("tv"):
23
+ # from pyfzf.pyfzf import FzfPrompt
24
+ # fzf_prompt = FzfPrompt()
25
+ # nl = "\n"
26
+ # 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, ' ')}")
27
+ # print("ch2")
28
+ from machineconfig.utils.accessories import randstr
29
+ options_txt_path = Path.home().joinpath("tmp_results/tmp_files/choices_" + randstr(6) + ".txt")
30
+ options_txt_path.parent.mkdir(parents=True, exist_ok=True)
31
+ options_txt_path.write_text("\n".join(options_strings), encoding="utf-8")
32
+
33
+ # Run `tv` interactively so the user can make selections. We redirect tv's
34
+ # stdout to a temporary output file so we can read the chosen lines after
35
+ # the interactive session completes. Do not capture_output or redirect
36
+ # stdin/stderr here so `tv` stays attached to the terminal.
37
+ tv_out_path = options_txt_path.with_name(options_txt_path.stem + "_out.txt")
38
+ tv_cmd = f"""cat {options_txt_path} | tv --preview-command "bat -n --color=always '{{}}'" --preview-size 30 --ansi true --source-output "{{strip_ansi}}" > {tv_out_path} """
39
+ res = subprocess.run(tv_cmd, shell=True)
40
+
41
+ # If tv returned a non-zero code and there is no output file, treat it as an error.
42
+ if res.returncode != 0 and not tv_out_path.exists():
43
+ raise RuntimeError(f"Got error running tv command: {tv_cmd}\nreturncode: {res.returncode}")
44
+
45
+ # Read selections (if any) from the output file created by tv.
46
+ out_text = tv_out_path.read_text(encoding="utf-8") if tv_out_path.exists() else ""
47
+ choice_string_multi = [x for x in out_text.splitlines() if x.strip() != ""]
48
+
49
+ # Cleanup temporary files
50
+ options_txt_path.unlink(missing_ok=True)
51
+ tv_out_path.unlink(missing_ok=True)
34
52
  if not multi:
35
53
  try:
36
54
  choice_one_string = choice_string_multi[0]
@@ -65,7 +83,7 @@ def choose_from_options[T](msg: str, options: Iterable[T], multi: bool, custom_i
65
83
  if choice_string == "":
66
84
  if default_string is None:
67
85
  console.print(Panel("🧨 Default option not available!", title="Error", expand=False))
68
- return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=multi, custom_input=custom_input)
86
+ return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, tv=tv, multi=multi, custom_input=custom_input)
69
87
  choice_idx = options_strings.index(default_string)
70
88
  assert default is not None, "🧨 Default option not available!"
71
89
  choice_one: T = default
@@ -83,7 +101,7 @@ def choose_from_options[T](msg: str, options: Iterable[T], multi: bool, custom_i
83
101
  _ = ie
84
102
  # raise ValueError(f"Unknown choice. {choice_string}") from ie
85
103
  console.print(Panel(f"❓ Unknown choice: '{choice_string}'", title="Error", expand=False))
86
- return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=multi, custom_input=custom_input)
104
+ return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, tv=tv, multi=multi, custom_input=custom_input)
87
105
  except (TypeError, ValueError) as te: # int(choice_string) failed due to # either the number is invalid, or the input is custom.
88
106
  if choice_string in options_strings: # string input
89
107
  choice_idx = options_strings.index(choice_one) # type: ignore
@@ -94,7 +112,7 @@ def choose_from_options[T](msg: str, options: Iterable[T], multi: bool, custom_i
94
112
  _ = te
95
113
  # raise ValueError(f"Unknown choice. {choice_string}") from te
96
114
  console.print(Panel(f"❓ Unknown choice: '{choice_string}'", title="Error", expand=False))
97
- return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=multi, custom_input=custom_input)
115
+ return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, tv=tv, multi=multi, custom_input=custom_input)
98
116
  console.print(Panel(f"✅ Selected option {choice_idx}: {choice_one}", title="Selected", expand=False))
99
117
  if multi:
100
118
  return [choice_one]
@@ -113,7 +131,7 @@ def choose_cloud_interactively() -> str:
113
131
  raise ValueError(f"Got {tmp} from rclone listremotes")
114
132
  if len(remotes) == 0:
115
133
  raise RuntimeError("You don't have remotes. Configure your rclone first to get cloud services access.")
116
- cloud: str = choose_from_options(msg="WHICH CLOUD?", multi=False, options=list(remotes), default=remotes[0], fzf=True)
134
+ cloud: str = choose_from_options(msg="WHICH CLOUD?", multi=False, options=list(remotes), default=remotes[0], tv=True)
117
135
  console.print(Panel(f"✅ SELECTED CLOUD | {cloud}", border_style="bold blue", expand=False))
118
136
  return cloud
119
137
 
@@ -131,4 +149,4 @@ def choose_ssh_host(multi: Literal[False]) -> str: ...
131
149
  @overload
132
150
  def choose_ssh_host(multi: Literal[True]) -> list[str]: ...
133
151
  def choose_ssh_host(multi: bool):
134
- return choose_from_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
152
+ return choose_from_options(msg="", options=get_ssh_hosts(), multi=multi, tv=True)