machineconfig 8.14__py3-none-any.whl → 8.50__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 (273) hide show
  1. machineconfig/cluster/remote/run_cluster.py +1 -1
  2. machineconfig/cluster/remote/run_remote.py +1 -1
  3. machineconfig/cluster/sessions_managers/utils/maker.py +10 -8
  4. machineconfig/cluster/sessions_managers/wt_local.py +1 -1
  5. machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
  6. machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
  7. machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
  8. machineconfig/jobs/installer/checks/check_installations.py +133 -0
  9. machineconfig/jobs/installer/checks/install_utils.py +132 -0
  10. machineconfig/jobs/installer/checks/report_utils.py +39 -0
  11. machineconfig/jobs/installer/checks/vt_utils.py +89 -0
  12. machineconfig/jobs/installer/installer_data.json +225 -140
  13. machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
  14. machineconfig/jobs/installer/package_groups.py +10 -9
  15. machineconfig/jobs/installer/python_scripts/boxes.py +1 -2
  16. machineconfig/jobs/installer/python_scripts/code.py +10 -8
  17. machineconfig/jobs/installer/python_scripts/hx.py +30 -13
  18. machineconfig/jobs/installer/python_scripts/nerfont_windows_helper.py +6 -5
  19. machineconfig/jobs/installer/python_scripts/sysabc.py +25 -19
  20. machineconfig/jobs/installer/python_scripts/yazi.py +33 -17
  21. machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
  22. machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +1 -1
  23. machineconfig/jobs/scripts_dynamic/a.py +413 -10
  24. machineconfig/profile/create_links.py +77 -20
  25. machineconfig/profile/create_links_export.py +63 -58
  26. machineconfig/profile/mapper_data.toml +30 -0
  27. machineconfig/profile/mapper_dotfiles.toml +253 -0
  28. machineconfig/scripts/python/agents.py +70 -172
  29. machineconfig/scripts/python/ai/initai.py +3 -1
  30. machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
  31. machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
  32. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +7 -5
  33. machineconfig/scripts/python/ai/solutions/claude/claude.py +1 -1
  34. machineconfig/scripts/python/ai/solutions/cline/cline.py +1 -1
  35. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +1 -1
  36. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +29 -0
  37. machineconfig/scripts/python/ai/solutions/crush/crush.py +1 -1
  38. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +1 -1
  39. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +1 -1
  40. machineconfig/scripts/python/ai/solutions/gemini/settings.json +3 -0
  41. machineconfig/scripts/python/ai/{solutions → utils}/generic.py +2 -15
  42. machineconfig/scripts/python/ai/utils/vscode_tasks.py +6 -3
  43. machineconfig/scripts/python/cloud.py +58 -11
  44. machineconfig/scripts/python/croshell.py +4 -156
  45. machineconfig/scripts/python/devops.py +57 -40
  46. machineconfig/scripts/python/devops_navigator.py +17 -3
  47. machineconfig/scripts/python/fire_jobs.py +8 -207
  48. machineconfig/scripts/python/ftpx.py +5 -225
  49. machineconfig/scripts/python/graph/cli_graph.json +8743 -0
  50. machineconfig/scripts/python/{env_manager → helper_env}/path_manager_tui.py +2 -2
  51. machineconfig/scripts/python/{env_manager → helpers/helper_env}/env_manager_tui.py +1 -1
  52. machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
  53. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.py +1 -1
  54. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
  55. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_gemini.py +1 -1
  56. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_qwen.py +1 -1
  57. machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
  58. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_launch.py +5 -5
  59. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +6 -6
  60. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +10 -5
  61. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +3 -3
  62. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
  63. machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +225 -0
  64. machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/scheduler.py +4 -4
  65. machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/start_slidev.py +7 -6
  66. machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
  67. machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +267 -0
  68. machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +98 -0
  69. machineconfig/scripts/python/helpers/helpers_devops/cli_config_dotfile.py +274 -0
  70. machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +76 -0
  71. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_nw.py +52 -72
  72. machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +274 -0
  73. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_self.py +40 -23
  74. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_file.py +44 -30
  75. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_server.py +26 -43
  76. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_terminal.py +12 -6
  77. machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
  78. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +12 -6
  79. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
  80. machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +68 -52
  81. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/run_script.py +75 -58
  82. machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.ps1 +41 -0
  83. machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.sh +48 -0
  84. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_wezterm_theme.py +3 -3
  85. machineconfig/scripts/python/helpers/helpers_fire_command/fire_jobs_impl.py +233 -0
  86. machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_route_helper.py +3 -3
  87. machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
  88. machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_linux/fzfg +4 -3
  89. machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
  90. machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_windows/fzfg.ps1 +1 -1
  91. machineconfig/scripts/python/helpers/helpers_navigator/__init__.py +20 -0
  92. machineconfig/scripts/python/helpers/helpers_navigator/cli_graph_loader.py +234 -0
  93. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/command_builder.py +61 -13
  94. machineconfig/scripts/python/helpers/helpers_navigator/command_detail.py +153 -0
  95. machineconfig/scripts/python/helpers/helpers_navigator/command_tree.py +45 -0
  96. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/data_models.py +18 -11
  97. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/main_app.py +5 -5
  98. machineconfig/scripts/python/helpers/helpers_network/__init__.py +0 -0
  99. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address.py +15 -17
  100. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address_switch.py +1 -1
  101. machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
  102. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_ssh.py +2 -2
  103. machineconfig/scripts/python/helpers/helpers_network/ssh_add_identity.py +73 -0
  104. machineconfig/scripts/python/helpers/helpers_network/ssh_add_ssh_key.py +175 -0
  105. machineconfig/scripts/python/helpers/helpers_network/ssh_debug_linux.py +319 -0
  106. machineconfig/scripts/python/helpers/helpers_network/ssh_debug_windows.py +275 -0
  107. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action.py +3 -3
  108. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action_helper.py +3 -3
  109. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/cloud_repo_sync.py +117 -33
  110. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +3 -2
  111. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -13
  112. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_2.py +63 -19
  113. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
  114. machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -0
  115. machineconfig/scripts/python/helpers/helpers_sessions/__init__.py +0 -0
  116. machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +186 -0
  117. machineconfig/scripts/python/{helpers_sessions → helpers/helpers_sessions}/sessions_multiprocess.py +1 -1
  118. machineconfig/scripts/python/helpers/helpers_terminal/__init__.py +0 -0
  119. machineconfig/scripts/python/helpers/helpers_terminal/terminal_impl.py +96 -0
  120. machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/download.py +1 -1
  121. machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/python.py +47 -26
  122. machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
  123. machineconfig/scripts/python/mcfg_entry.py +133 -48
  124. machineconfig/scripts/python/msearch.py +15 -61
  125. machineconfig/scripts/python/sessions.py +59 -194
  126. machineconfig/scripts/python/terminal.py +18 -96
  127. machineconfig/scripts/python/utils.py +101 -20
  128. machineconfig/settings/atuin/config.toml +294 -0
  129. machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
  130. machineconfig/settings/linters/.ruff.toml +1 -0
  131. machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
  132. machineconfig/settings/shells/bash/init.sh +6 -3
  133. machineconfig/settings/shells/pwsh/init.ps1 +69 -1
  134. machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
  135. machineconfig/settings/shells/wezterm/wezterm.lua +4 -1
  136. machineconfig/settings/shells/wt/settings.json +20 -7
  137. machineconfig/settings/shells/zsh/init.sh +25 -4
  138. machineconfig/settings/television/cable_unix/bash-history.toml +1 -1
  139. machineconfig/settings/television/cable_windows/pwsh-history.toml +1 -1
  140. machineconfig/settings/tv/config.toml +234 -0
  141. machineconfig/settings/tv/themes/catppuccin-mocha-sky.toml +22 -0
  142. machineconfig/settings/wsl/.wslconfig +5 -30
  143. machineconfig/settings/yazi/yazi_linux.toml +18 -8
  144. machineconfig/settings/zellij/layouts/st.kdl +2 -2
  145. machineconfig/settings/zellij/layouts/st2.kdl +1 -1
  146. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  147. machineconfig/setup_linux/web_shortcuts/live_from_github.sh +3 -0
  148. machineconfig/setup_mac/__init__.py +0 -2
  149. machineconfig/setup_windows/__init__.py +0 -1
  150. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +14 -13
  151. machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +4 -3
  152. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -3
  153. machineconfig/type_hinting/sql/__init__.py +1 -0
  154. machineconfig/type_hinting/sql/base.py +216 -0
  155. machineconfig/type_hinting/sql/core_schema.py +64 -0
  156. machineconfig/type_hinting/sql/core_schema_typeddict.py +41 -0
  157. machineconfig/type_hinting/sql/typeddict_codegen.py +222 -0
  158. machineconfig/type_hinting/typedict/__init__.py +1 -0
  159. machineconfig/type_hinting/typedict/ast_utils.py +130 -0
  160. machineconfig/type_hinting/typedict/generator_helpers.py +319 -0
  161. machineconfig/type_hinting/typedict/generators.py +231 -0
  162. machineconfig/type_hinting/typedict/polars_schema.py +24 -0
  163. machineconfig/type_hinting/typedict/polars_schema_typeddict.py +63 -0
  164. machineconfig/utils/accessories.py +24 -0
  165. machineconfig/utils/code.py +41 -13
  166. machineconfig/utils/files/ascii_art.py +10 -14
  167. machineconfig/utils/files/headers.py +3 -5
  168. machineconfig/utils/files/read.py +8 -1
  169. machineconfig/utils/installer_utils/github_release_bulk.py +11 -91
  170. machineconfig/utils/installer_utils/github_release_scraper.py +99 -0
  171. machineconfig/utils/installer_utils/install_from_url.py +1 -1
  172. machineconfig/utils/installer_utils/installer_class.py +12 -4
  173. machineconfig/utils/installer_utils/installer_cli.py +1 -15
  174. machineconfig/utils/installer_utils/installer_helper.py +2 -2
  175. machineconfig/utils/installer_utils/installer_locator_utils.py +13 -13
  176. machineconfig/utils/installer_utils/installer_runner.py +4 -4
  177. machineconfig/utils/io.py +25 -8
  178. machineconfig/utils/meta.py +6 -4
  179. machineconfig/utils/options.py +49 -19
  180. machineconfig/utils/options_utils/__init__.py +0 -0
  181. machineconfig/utils/options_utils/options_tv_linux.py +211 -0
  182. machineconfig/utils/options_utils/options_tv_windows.py +88 -0
  183. machineconfig/utils/options_utils/tv_options.py +37 -0
  184. machineconfig/utils/path_extended.py +6 -6
  185. machineconfig/utils/scheduler.py +8 -2
  186. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
  187. machineconfig/utils/source_of_truth.py +6 -1
  188. machineconfig/utils/ssh.py +69 -18
  189. machineconfig/utils/ssh_utils/abc.py +1 -1
  190. machineconfig/utils/ssh_utils/copy_from_here.py +17 -12
  191. machineconfig/utils/ssh_utils/utils.py +21 -5
  192. machineconfig/utils/ssh_utils/wsl.py +107 -170
  193. machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
  194. machineconfig/utils/upgrade_packages.py +4 -8
  195. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/METADATA +29 -22
  196. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/RECORD +251 -211
  197. machineconfig/jobs/installer/check_installations.py +0 -248
  198. machineconfig/profile/backup.toml +0 -49
  199. machineconfig/profile/mapper.toml +0 -263
  200. machineconfig/scripts/python/helpers_devops/cli_config.py +0 -105
  201. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -89
  202. machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
  203. machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -208
  204. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +0 -80
  205. machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +0 -3
  206. machineconfig/scripts/python/helpers_navigator/__init__.py +0 -20
  207. machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
  208. machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -620
  209. machineconfig/scripts/python/helpers_network/ssh_add_identity.py +0 -116
  210. machineconfig/scripts/python/helpers_network/ssh_add_ssh_key.py +0 -153
  211. machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +0 -391
  212. machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +0 -338
  213. machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -77
  214. machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
  215. machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
  216. machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
  217. machineconfig/utils/options_tv.py +0 -119
  218. machineconfig/utils/tst.py +0 -20
  219. /machineconfig/{scripts/python/helpers_agents → jobs/installer/checks}/__init__.py +0 -0
  220. /machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
  221. /machineconfig/scripts/python/{helpers_agents/agentic_frameworks → graph}/__init__.py +0 -0
  222. /machineconfig/scripts/python/{helpers_cloud → helpers}/__init__.py +0 -0
  223. /machineconfig/scripts/python/{env_manager → helpers/helper_env}/__init__.py +0 -0
  224. /machineconfig/scripts/python/{env_manager → helpers/helper_env}/path_manager_backend.py +0 -0
  225. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_agents}/__init__.py +0 -0
  226. /machineconfig/scripts/python/{helpers_devops → helpers/helpers_agents/agentic_frameworks}/__init__.py +0 -0
  227. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
  228. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
  229. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_helper_types.py +0 -0
  230. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
  231. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aichat/config.yaml +0 -0
  232. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aider/.aider.conf.yml +0 -0
  233. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/copilot/config.yml +0 -0
  234. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/crush/crush.json +0 -0
  235. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/gemini/settings.json +0 -0
  236. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/privacy.py +0 -0
  237. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/prompt.txt +0 -0
  238. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.ps1 +0 -0
  239. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.sh +0 -0
  240. /machineconfig/scripts/python/{helpers_devops/themes → helpers/helpers_cloud}/__init__.py +0 -0
  241. /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +0 -0
  242. /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
  243. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_croshell}/__init__.py +0 -0
  244. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.py +0 -0
  245. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/pomodoro.py +0 -0
  246. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer.py +0 -0
  247. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer_template.py +0 -0
  248. /machineconfig/scripts/python/{helpers_network → helpers/helpers_devops}/__init__.py +0 -0
  249. /machineconfig/scripts/python/{helpers_sessions → helpers/helpers_devops/themes}/__init__.py +0 -0
  250. /machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
  251. /machineconfig/scripts/python/{helpers_devops/themes/choose_starship_theme.ps1 → helpers/helpers_fire_command/__init__.py} +0 -0
  252. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/cloud_manager.py +0 -0
  253. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/f.py +0 -0
  254. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/file_wrangler.py +0 -0
  255. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +0 -0
  256. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_streamlit_helper.py +0 -0
  257. /machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/__init__.py +0 -0
  258. /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
  259. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nfs.py +0 -0
  260. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nw_drive.py +0 -0
  261. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/onetimeshare.py +0 -0
  262. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/wifi_conn.py +0 -0
  263. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -0
  264. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_1.py +0 -0
  265. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
  266. /machineconfig/scripts/python/helpers/{ast_search.py → helpers_search/ast_search.py} +0 -0
  267. /machineconfig/scripts/python/helpers/{qr_code.py → helpers_search/qr_code.py} +0 -0
  268. /machineconfig/scripts/python/helpers/{repo_rag.py → helpers_search/repo_rag.py} +0 -0
  269. /machineconfig/scripts/python/helpers/{symantic_search.py → helpers_search/symantic_search.py} +0 -0
  270. /machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/pdf.py +0 -0
  271. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/WHEEL +0 -0
  272. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/entry_points.txt +0 -0
  273. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,21 @@
1
-
2
1
  from typing import Optional, TypedDict, cast
3
2
 
3
+
4
4
  class CountryFlag(TypedDict, total=False):
5
5
  emoji: str
6
6
  unicode: str
7
7
 
8
+
8
9
  class CountryCurrency(TypedDict, total=False):
9
10
  code: str
10
11
  symbol: str
11
12
 
13
+
12
14
  class Continent(TypedDict, total=False):
13
15
  code: str
14
16
  name: str
15
17
 
18
+
16
19
  class PublicIpInfo(TypedDict, total=True):
17
20
  ip: str
18
21
  hostname: str
@@ -33,15 +36,11 @@ class PublicIpInfo(TypedDict, total=True):
33
36
  def get_public_ip_address() -> PublicIpInfo:
34
37
  from machineconfig.utils.installer_utils.installer_cli import install_if_missing
35
38
  import subprocess
39
+
36
40
  install_if_missing("ipinfo")
37
- result = subprocess.run(
38
- ["ipinfo", "myip", "--json"],
39
- check=True,
40
- capture_output=True,
41
- text=True,
42
- encoding="utf-8",
43
- )
41
+ result = subprocess.run(["ipinfo", "myip", "--json"], check=True, capture_output=True, text=True, encoding="utf-8")
44
42
  import json
43
+
45
44
  loaded_json: PublicIpInfo = json.loads(result.stdout)
46
45
  return loaded_json
47
46
 
@@ -49,6 +48,7 @@ def get_public_ip_address() -> PublicIpInfo:
49
48
  def get_all_ipv4_addresses() -> list[tuple[str, str]]:
50
49
  import psutil
51
50
  import socket
51
+
52
52
  result: list[tuple[str, str]] = []
53
53
  for iface, addrs in psutil.net_if_addrs().items():
54
54
  for addr in addrs:
@@ -82,19 +82,16 @@ def select_lan_ipv4(prefer_vpn: bool) -> Optional[str]:
82
82
  )
83
83
 
84
84
  # Light preference for names that look like real NICs
85
- PHYSICAL_IFACE_PAT = re.compile(
86
- r"^(?:eth\d*|en\d*|enp.*|ens.*|eno.*|wlan\d*|wl.*|.*wifi.*|.*ethernet.*)$",
87
- re.IGNORECASE,
88
- )
85
+ PHYSICAL_IFACE_PAT = re.compile(r"^(?:eth\d*|en\d*|enp.*|ens.*|eno.*|wlan\d*|wl.*|.*wifi.*|.*ethernet.*)$", re.IGNORECASE)
89
86
 
90
87
  # Known noisy CIDRs to avoid
91
88
  NOISY_NETS: list[ipaddress.IPv4Network] = [
92
- ipaddress.IPv4Network("100.64.0.0/10"), # CGNAT (Tailscale/others)
93
- ipaddress.IPv4Network("172.17.0.0/16"), # docker0 default
89
+ ipaddress.IPv4Network("100.64.0.0/10"), # CGNAT (Tailscale/others)
90
+ ipaddress.IPv4Network("172.17.0.0/16"), # docker0 default
94
91
  ipaddress.IPv4Network("172.18.0.0/16"),
95
92
  ipaddress.IPv4Network("172.19.0.0/16"),
96
- ipaddress.IPv4Network("192.168.49.0/24"), # minikube default
97
- ipaddress.IPv4Network("10.0.2.0/24"), # VirtualBox NAT
93
+ ipaddress.IPv4Network("192.168.49.0/24"), # minikube default
94
+ ipaddress.IPv4Network("10.0.2.0/24"), # VirtualBox NAT
98
95
  ]
99
96
 
100
97
  def _in_any(ip: ipaddress.IPv4Address, nets: Sequence[ipaddress.IPv4Network]) -> bool:
@@ -102,8 +99,9 @@ def select_lan_ipv4(prefer_vpn: bool) -> Optional[str]:
102
99
 
103
100
  stats = psutil.net_if_stats()
104
101
  best = None
105
- best_score = -10**9
102
+ best_score = -(10**9)
106
103
  import socket
104
+
107
105
  for iface, addrs in psutil.net_if_addrs().items():
108
106
  st = stats.get(iface)
109
107
  if not st or not st.isup:
@@ -1,7 +1,7 @@
1
1
  import subprocess
2
2
  import time
3
3
 
4
- from machineconfig.scripts.python.helpers_network.address import get_public_ip_address
4
+ from machineconfig.scripts.python.helpers.helpers_network.address import get_public_ip_address
5
5
 
6
6
 
7
7
  def switch_public_ip_address(max_trials: int = 10, wait_seconds: float = 4.0) -> None:
@@ -0,0 +1,276 @@
1
+ """Pure Python implementation for ftpx command - no typer dependencies."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+
7
+ def ftpx(source: str, target: str, recursive: bool, zipFirst: bool, cloud: bool, overwrite_existing: bool) -> None:
8
+ """File transfer utility through SSH."""
9
+ if target == "wsl" or source == "wsl":
10
+ _handle_wsl_transfer(source=source, target=target, overwrite_existing=overwrite_existing)
11
+ return
12
+ elif source == "win" or target == "win":
13
+ _handle_win_transfer(source=source, target=target, overwrite_existing=overwrite_existing, windows_username=None)
14
+ return
15
+
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+
19
+ console = Console()
20
+
21
+ console.print(
22
+ Panel(
23
+ "\n".join(["🚀 FTP File Transfer", "📋 Starting transfer process..."]),
24
+ title="Transfer Initialisation",
25
+ border_style="blue",
26
+ padding=(1, 2),
27
+ )
28
+ )
29
+
30
+ resolved_source, resolved_target, machine, source_is_remote = _resolve_paths(source=source, target=target)
31
+
32
+ from machineconfig.utils.accessories import pprint
33
+
34
+ pprint({"source": str(resolved_source), "target": str(resolved_target), "machine": machine}, "CLI Resolution")
35
+
36
+ ssh = _create_ssh_connection(machine=machine, console=console)
37
+
38
+ if cloud:
39
+ received_file = _handle_cloud_transfer(ssh=ssh, resolved_source=resolved_source, resolved_target=resolved_target, console=console)
40
+ else:
41
+ received_file = _handle_direct_transfer(
42
+ ssh=ssh,
43
+ resolved_source=resolved_source,
44
+ resolved_target=resolved_target,
45
+ source_is_remote=source_is_remote,
46
+ zipFirst=zipFirst,
47
+ recursive=recursive,
48
+ overwrite_existing=overwrite_existing,
49
+ console=console,
50
+ )
51
+ if source_is_remote and received_file is not None:
52
+ console.print(
53
+ Panel(
54
+ "\n".join(["📁 File Received", f"Parent: [cyan]{repr(received_file.parent)}[/cyan]", f"File: [cyan]{repr(received_file)}[/cyan]"]),
55
+ title="Transfer Result",
56
+ border_style="green",
57
+ padding=(1, 2),
58
+ )
59
+ )
60
+ console.print(Panel("File transfer process finished successfully", title="✅ Transfer Complete", border_style="green", padding=(1, 2)))
61
+
62
+
63
+ def _handle_wsl_transfer(source: str, target: str, overwrite_existing: bool) -> None:
64
+ """Handle WSL transfer when inside Windows."""
65
+ from machineconfig.utils.ssh_utils.wsl import copy_when_inside_windows
66
+
67
+ if target == "wsl":
68
+ target_obj = Path(source).expanduser().absolute().relative_to(Path.home())
69
+ source_obj = target_obj
70
+ else:
71
+ source_obj = Path(target).expanduser().absolute().relative_to(Path.home())
72
+ target_obj = source_obj
73
+ copy_when_inside_windows(source_obj, target_obj, overwrite_existing)
74
+
75
+
76
+ def _handle_win_transfer(source: str, target: str, overwrite_existing: bool, windows_username: str | None) -> None:
77
+ """Handle Windows transfer when inside WSL."""
78
+ from machineconfig.utils.ssh_utils.wsl import copy_when_inside_wsl
79
+
80
+ if source == "win":
81
+ source_obj = Path(target).expanduser().absolute().relative_to(Path.home())
82
+ target_obj = source_obj
83
+ else:
84
+ target_obj = Path(source).expanduser().absolute().relative_to(Path.home())
85
+ source_obj = target_obj
86
+ copy_when_inside_wsl(source_obj, target_obj, overwrite_existing, windows_username=windows_username)
87
+
88
+
89
+ def _resolve_paths(source: str, target: str) -> tuple[Optional[str], Optional[str], str, bool]:
90
+ """Resolve source and target paths, determine machine and direction."""
91
+ from machineconfig.utils.path_extended import PathExtended
92
+ from machineconfig.scripts.python.helpers.helpers_cloud.helpers2 import ES
93
+
94
+ resolved_source: Optional[str] = None
95
+ resolved_target: Optional[str] = None
96
+ machine: str = ""
97
+ source_is_remote: bool = False
98
+
99
+ if ":" in source and (source[1] != ":" if len(source) > 1 else True):
100
+ source_is_remote = True
101
+ source_parts = source.split(":")
102
+ machine = source_parts[0]
103
+ if len(source_parts) > 1 and source_parts[1] == ES:
104
+ if target == ES:
105
+ raise ValueError(f"""
106
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
107
+ ┃ ❌ Configuration Error
108
+ ┃ Cannot use expand symbol `{ES}` in both source and target
109
+ ┃ This creates a cyclical inference dependency
110
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
111
+ else:
112
+ target_path_obj = PathExtended(target).expanduser().absolute()
113
+ resolved_source = target_path_obj.collapseuser().as_posix()
114
+ resolved_target = target
115
+ else:
116
+ resolved_source = ":".join(source.split(":")[1:])
117
+ if target == ES:
118
+ resolved_target = None
119
+ else:
120
+ resolved_target = PathExtended(target).expanduser().absolute().as_posix()
121
+
122
+ elif ":" in target and (target[1] != ":" if len(target) > 1 else True):
123
+ source_is_remote = False
124
+ target_parts = target.split(":")
125
+ machine = target_parts[0]
126
+ if len(target_parts) > 1 and target_parts[1] == ES:
127
+ if source == ES:
128
+ raise ValueError(f"""
129
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
130
+ ┃ ❌ Configuration Error
131
+ ┃ Cannot use expand symbol `{ES}` in both source and target
132
+ ┃ This creates a cyclical inference dependency
133
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
134
+ else:
135
+ resolved_source = source
136
+ resolved_target = None
137
+ else:
138
+ resolved_target = ":".join(target.split(":")[1:])
139
+ if source == ES:
140
+ resolved_source = None
141
+ else:
142
+ resolved_source = PathExtended(source).expanduser().absolute().as_posix()
143
+ else:
144
+ raise ValueError("""
145
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
146
+ ┃ ❌ Path Error
147
+ ┃ Either source or target must be a remote path
148
+ ┃ Format should be: machine:path
149
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
150
+
151
+ return resolved_source, resolved_target, machine, source_is_remote
152
+
153
+
154
+ def _create_ssh_connection(machine: str, console: "Console") -> "SSH": # type: ignore[name-defined]
155
+ """Create SSH connection, handling authentication."""
156
+ from machineconfig.utils.ssh import SSH
157
+ from paramiko.ssh_exception import AuthenticationException # type: ignore
158
+ from rich.panel import Panel
159
+
160
+ try:
161
+ ssh = SSH(host=rf"{machine}", username=None, hostname=None, ssh_key_path=None, password=None, port=22, enable_compression=True)
162
+ except AuthenticationException:
163
+ console.print(
164
+ Panel(
165
+ "\n".join(
166
+ [
167
+ "🔑 Authentication failed. Trying manual authentication...",
168
+ "⚠️ Ensure that the username is provided correctly; only password prompts are handled here.",
169
+ ]
170
+ ),
171
+ title="Authentication Required",
172
+ border_style="yellow",
173
+ padding=(1, 2),
174
+ )
175
+ )
176
+ import getpass
177
+
178
+ pwd = getpass.getpass()
179
+ ssh = SSH(host=rf"{machine}", username=None, hostname=None, ssh_key_path=None, password=pwd, port=22, enable_compression=True)
180
+
181
+ return ssh
182
+
183
+
184
+ def _handle_cloud_transfer(
185
+ ssh: "SSH", resolved_source: Optional[str], resolved_target: Optional[str], console: "Console"
186
+ ) -> Optional["PathExtended"]: # type: ignore[name-defined]
187
+ """Handle cloud transfer mode."""
188
+ from machineconfig.utils.path_extended import PathExtended
189
+ from rich.panel import Panel
190
+
191
+ console.print(Panel.fit("☁️ Cloud transfer mode — uploading from remote to cloud...", title="Cloud Upload", border_style="cyan"))
192
+ ssh.run_shell_cmd_on_remote(
193
+ command=f"cloud copy {resolved_source} :^",
194
+ verbose_output=True,
195
+ description="Uploading from remote to the cloud.",
196
+ strict_stderr=False,
197
+ strict_return_code=False,
198
+ )
199
+ console.print(Panel.fit("⬇️ Cloud transfer mode — downloading from cloud to local...", title="Cloud Download", border_style="cyan"))
200
+ ssh.run_shell_cmd_on_local(command=f"cloud copy :^ {resolved_target}")
201
+ return PathExtended(resolved_target) # type: ignore
202
+
203
+
204
+ def _handle_direct_transfer(
205
+ ssh: "SSH",
206
+ resolved_source: Optional[str],
207
+ resolved_target: Optional[str],
208
+ source_is_remote: bool,
209
+ zipFirst: bool,
210
+ recursive: bool,
211
+ overwrite_existing: bool,
212
+ console: "Console",
213
+ ) -> Optional["PathExtended"]: # type: ignore[name-defined]
214
+ """Handle direct SSH transfer."""
215
+ from rich.panel import Panel
216
+
217
+ if source_is_remote:
218
+ if resolved_source is None:
219
+ print("❌ Path Error: Source must be a remote path (machine:path)")
220
+ return None
221
+ target_display = resolved_target or "<auto>"
222
+ console.print(
223
+ Panel(
224
+ "\n".join(
225
+ [
226
+ "📥 Transfer Mode: Remote → Local",
227
+ f"Source: [cyan]{resolved_source}[/cyan]",
228
+ f"Target: [cyan]{target_display}[/cyan]",
229
+ f"Options: {'ZIP compression' if zipFirst else 'No compression'}, {'Recursive' if recursive else 'Non-recursive'}",
230
+ ]
231
+ ),
232
+ title="Transfer Details",
233
+ border_style="cyan",
234
+ padding=(1, 2),
235
+ )
236
+ )
237
+ received_file = ssh.copy_to_here(source=resolved_source, target=resolved_target, compress_with_zip=zipFirst, recursive=recursive)
238
+ else:
239
+ assert resolved_source is not None, "❌ Path Error: Target must be a remote path (machine:path)"
240
+ target_display = resolved_target or "<auto>"
241
+ console.print(
242
+ Panel(
243
+ "\n".join(
244
+ [
245
+ "📤 Transfer Mode: Local → Remote",
246
+ f"Source: [cyan]{resolved_source}[/cyan]",
247
+ f"Target: [cyan]{target_display}[/cyan]",
248
+ f"Options: {'ZIP compression' if zipFirst else 'No compression'}, {'Recursive' if recursive else 'Non-recursive'}",
249
+ ]
250
+ ),
251
+ title="Transfer Details",
252
+ border_style="cyan",
253
+ padding=(1, 2),
254
+ )
255
+ )
256
+ received_file = ssh.copy_from_here(
257
+ source_path=resolved_source,
258
+ target_rel2home=resolved_target,
259
+ compress_with_zip=zipFirst,
260
+ recursive=recursive,
261
+ overwrite_existing=overwrite_existing,
262
+ )
263
+
264
+ return received_file
265
+
266
+
267
+ if __name__ == "__main__":
268
+ from machineconfig.utils.ssh import SSH
269
+
270
+ _ = SSH
271
+ from machineconfig.utils.path_extended import PathExtended
272
+
273
+ _ = PathExtended
274
+ from rich.console import Console
275
+
276
+ _ = Console
@@ -3,7 +3,7 @@
3
3
  from platform import system
4
4
  import subprocess
5
5
  from machineconfig.utils.ssh import SSH
6
- from machineconfig.utils.path_extended import PathExtended
6
+ from pathlib import Path
7
7
 
8
8
  from machineconfig.utils.options import choose_ssh_host
9
9
 
@@ -35,7 +35,7 @@ def main():
35
35
 
36
36
  mount_point = input(f"📂 Enter the mount point directory (e.g., /mnt/network) [Default: ~/data/mount_ssh/{ssh.hostname}]: ")
37
37
  if mount_point == "":
38
- mount_point = PathExtended.home().joinpath(rf"data/mount_ssh/{ssh.hostname}")
38
+ mount_point = Path.home().joinpath(rf"data/mount_ssh/{ssh.hostname}")
39
39
 
40
40
  print(f"\n📁 Mount Point: {mount_point}")
41
41
 
@@ -0,0 +1,73 @@
1
+ r"""ID
2
+
3
+ On windows:
4
+
5
+ $sshfile = "$env:USERPROFILE/.ssh/id_rsa"
6
+ Set-Service ssh-agent -StartupType Manual # allow the service to be started manually
7
+ ssh-agent # start the service
8
+ ssh-add.exe $sshfile # add the key to the agent
9
+
10
+ # copy ssh key:
11
+ # This is the Windows equivalent of copy-ssh-id on Linux.
12
+ # Just like the original function, it is a convenient way of doing two things in one go:
13
+ # 1- copy a certain public key to the remote machine.
14
+ # scp ~/.ssh/id_rsa.pub $remote_user@$remote_host:~/.ssh/authorized_keys
15
+ # 2- Store the value on the remote in a file called .ssh/authorized_keys
16
+ # ssh $remote_user@$remote_host "echo $public_key >> ~/.ssh/authorized_keys"
17
+ # Idea from: https://www.chrisjhart.com/Windows-10-ssh-copy-id/
18
+
19
+
20
+
21
+ """
22
+
23
+ from machineconfig.utils.path_extended import PathExtended
24
+ from machineconfig.utils.options import choose_from_options
25
+ from rich.console import Console
26
+ from rich.panel import Panel
27
+
28
+
29
+ console = Console()
30
+
31
+
32
+ def main() -> None:
33
+ private_keys = [PathExtended(x).with_name(x.stem) for x in PathExtended.home().joinpath(".ssh").glob("*.pub")]
34
+ key_status = f"Found {len(private_keys)} key(s)" if private_keys else "No keys found"
35
+ console.print(Panel(f"🔑 SSH Identity Management\n🔍 {key_status} in ~/.ssh", title="[bold blue]Setup[/bold blue]", expand=False))
36
+
37
+ choice = choose_from_options(msg="Path to private key to be used when ssh'ing: ", options=[str(x) for x in private_keys] + ["I have the path to the key file", "I want to paste the key itself"], multi=False)
38
+
39
+ if choice == "I have the path to the key file":
40
+ path_to_key = PathExtended(input("📋 Enter path to private key: ")).expanduser().absolute()
41
+ elif choice == "I want to paste the key itself":
42
+ key_filename = input("📝 File name (default: my_pasted_key): ") or "my_pasted_key"
43
+ path_to_key = PathExtended.home().joinpath(f".ssh/{key_filename}")
44
+ path_to_key.parent.mkdir(parents=True, exist_ok=True)
45
+ path_to_key.write_text(input("🔑 Paste the private key: "), encoding="utf-8")
46
+ else:
47
+ path_to_key = PathExtended(choice)
48
+
49
+ txt = f"IdentityFile {path_to_key.collapseuser().as_posix()}"
50
+ config_path = PathExtended.home().joinpath(".ssh/config")
51
+
52
+ if config_path.exists():
53
+ current = config_path.read_text(encoding="utf-8")
54
+ config_action = "updated"
55
+ else:
56
+ current = txt
57
+ config_action = "created"
58
+ lines = current.split("\n")
59
+ found = False
60
+ for i, line in enumerate(lines):
61
+ if txt in line:
62
+ lines[i] = txt
63
+ found = True
64
+ if not found:
65
+ lines.insert(0, txt)
66
+ config_path.write_text("\n".join(lines), encoding="utf-8")
67
+
68
+ console.print(Panel(f"✅ Identity: {path_to_key.name}\n📄 Config {config_action}: {config_path}", title="[bold green]Complete[/bold green]", expand=False, border_style="green"))
69
+ return None
70
+
71
+
72
+ if __name__ == "__main__":
73
+ pass
@@ -0,0 +1,175 @@
1
+ """SSH"""
2
+
3
+ from platform import system
4
+ from pathlib import Path
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich import box
8
+ from typing import Optional, Annotated
9
+ import typer
10
+ import subprocess
11
+
12
+
13
+ console = Console()
14
+
15
+
16
+ def _add_ssh_key_windows(path_to_key: Path) -> None:
17
+ """Add SSH key on Windows using Python with proper UTF-8 encoding.
18
+ This replaces the PowerShell script that was writing UTF-16LE encoded files which openssh server cannot read.
19
+ """
20
+ sshd_dir = Path("C:/ProgramData/ssh")
21
+ admin_auth_keys = sshd_dir / "administrators_authorized_keys"
22
+ sshd_config = sshd_dir / "sshd_config"
23
+ key_content = path_to_key.read_text(encoding="utf-8").strip()
24
+ if admin_auth_keys.exists():
25
+ existing = admin_auth_keys.read_text(encoding="utf-8")
26
+ if not existing.endswith("\n"):
27
+ existing += "\n"
28
+ admin_auth_keys.write_text(existing + key_content + "\n", encoding="utf-8")
29
+ else:
30
+ admin_auth_keys.write_text(key_content + "\n", encoding="utf-8")
31
+ icacls_cmd = f'icacls "{admin_auth_keys}" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"'
32
+ subprocess.run(icacls_cmd, shell=True, check=True)
33
+ if sshd_config.exists():
34
+ config_text = sshd_config.read_text(encoding="utf-8")
35
+ config_text = config_text.replace("#PubkeyAuthentication", "PubkeyAuthentication")
36
+ sshd_config.write_text(config_text, encoding="utf-8")
37
+ subprocess.run("Restart-Service sshd -Force", shell=True, check=True)
38
+
39
+
40
+ def get_add_ssh_key_script(path_to_key: Path, verbose: bool = True) -> tuple[str, str]:
41
+ """Returns (program_script, status_message) tuple. For Windows, program_script is empty because we handle it in Python."""
42
+ os_name = system()
43
+ if os_name == "Linux" or os_name == "Darwin":
44
+ authorized_keys = Path.home().joinpath(".ssh/authorized_keys")
45
+ os_icon, os_label = "🐧", "Linux/macOS"
46
+ elif os_name == "Windows":
47
+ authorized_keys = Path("C:/ProgramData/ssh/administrators_authorized_keys")
48
+ os_icon, os_label = "🪟", "Windows"
49
+ else:
50
+ raise NotImplementedError("Only Linux, macOS and Windows are supported")
51
+
52
+ status_lines: list[str] = [f"{os_icon} {os_label} │ Auth file: {authorized_keys}"]
53
+ program = ""
54
+
55
+ if authorized_keys.exists():
56
+ keys_text = authorized_keys.read_text(encoding="utf-8").split("\n")
57
+ key_count = len([k for k in keys_text if k.strip()])
58
+ status_lines.append(f"🔑 Existing keys: {key_count}")
59
+ if path_to_key.read_text(encoding="utf-8") in authorized_keys.read_text(encoding="utf-8"):
60
+ status_lines.append(f"⚠️ Key [yellow]{path_to_key.name}[/yellow] already authorized, skipping")
61
+ else:
62
+ status_lines.append(f"➕ Adding: [green]{path_to_key.name}[/green]")
63
+ if os_name == "Linux" or os_name == "Darwin":
64
+ program = f"cat {path_to_key} >> ~/.ssh/authorized_keys"
65
+ elif os_name == "Windows":
66
+ _add_ssh_key_windows(path_to_key)
67
+ else:
68
+ raise NotImplementedError
69
+ else:
70
+ status_lines.append(f"📝 Creating auth file with: [green]{path_to_key.name}[/green]")
71
+ if os_name == "Linux" or os_name == "Darwin":
72
+ program = f"cat {path_to_key} > ~/.ssh/authorized_keys"
73
+ else:
74
+ _add_ssh_key_windows(path_to_key)
75
+
76
+ if os_name == "Linux" or os_name == "Darwin":
77
+ program += """
78
+ sudo chmod 700 ~/.ssh
79
+ sudo chmod 644 ~/.ssh/authorized_keys
80
+ sudo chmod 644 ~/.ssh/*.pub
81
+ sudo service ssh --full-restart
82
+ # from superuser.com/questions/215504/permissions-on-private-key-in-ssh-folder
83
+ """
84
+ return program, "\n".join(status_lines)
85
+
86
+
87
+ """
88
+ Common pitfalls:
89
+ 🚫 Wrong line endings (LF/CRLF) in config files
90
+ 🌐 Network port conflicts (try 2222 -> 2223) between WSL and Windows
91
+ sudo service ssh restart
92
+ sudo service ssh status
93
+ sudo nano /etc/ssh/sshd_config
94
+ """
95
+
96
+
97
+ def main(pub_path: Annotated[Optional[str], typer.Argument(help="Path to the public key file")] = None,
98
+ pub_choose: Annotated[bool, typer.Option("--choose", "-c", help="Choose from available public keys in ~/.ssh")] = False,
99
+ pub_val: Annotated[bool, typer.Option("--paste", "-p", help="Paste the public key content manually")] = False,
100
+ from_github: Annotated[Optional[str], typer.Option("--from-github", "-g", help="Fetch public keys from a GitHub username")] = None
101
+ ) -> None:
102
+ info_lines: list[str] = []
103
+ program = ""
104
+ status_msg = ""
105
+
106
+ if pub_path:
107
+ key_path = Path(pub_path).expanduser().absolute()
108
+ key_path.parent.mkdir(parents=True, exist_ok=True)
109
+ if not key_path.exists():
110
+ console.print(Panel(f"❌ Key path does not exist: {key_path}", title="[bold red]Error[/bold red]", border_style="red"))
111
+ raise typer.Exit(code=1)
112
+ info_lines.append(f"📄 Source: Local file │ {key_path}")
113
+ program, status_msg = get_add_ssh_key_script(key_path)
114
+
115
+ elif pub_choose:
116
+ pub_keys = list(Path.home().joinpath(".ssh").glob("*.pub"))
117
+ if not pub_keys:
118
+ console.print(Panel("⚠️ No public keys found in ~/.ssh", title="[bold yellow]Warning[/bold yellow]", border_style="yellow"))
119
+ return
120
+ info_lines.append(f"📄 Source: Local ~/.ssh │ Found {len(pub_keys)} key(s)")
121
+ programs: list[str] = []
122
+ statuses: list[str] = []
123
+ for key in pub_keys:
124
+ p, s = get_add_ssh_key_script(key)
125
+ programs.append(p)
126
+ statuses.append(s)
127
+ program = "\n\n\n".join(programs)
128
+ status_msg = "\n".join(statuses)
129
+
130
+ elif pub_val:
131
+ key_filename = input("📝 File name (default: my_pasted_key.pub): ") or "my_pasted_key.pub"
132
+ key_path = Path.home().joinpath(f".ssh/{key_filename}")
133
+ key_path.parent.mkdir(parents=True, exist_ok=True)
134
+ key_path.write_text(input("🔑 Paste the public key here: "), encoding="utf-8")
135
+ info_lines.append(f"📄 Source: Pasted │ Saved to {key_path}")
136
+ program, status_msg = get_add_ssh_key_script(key_path)
137
+
138
+ elif from_github:
139
+ import requests
140
+ response = requests.get(f"https://api.github.com/users/{from_github}/keys")
141
+ if response.status_code != 200:
142
+ console.print(Panel(f"❌ GitHub API error for user '{from_github}' │ Status: {response.status_code}", title="[bold red]Error[/bold red]", border_style="red"))
143
+ raise typer.Exit(code=1)
144
+ keys = response.json()
145
+ if not keys:
146
+ console.print(Panel(f"⚠️ No public keys found for GitHub user: {from_github}", title="[bold yellow]Warning[/bold yellow]", border_style="yellow"))
147
+ return
148
+ key_path = Path.home().joinpath(f".ssh/{from_github}_github_keys.pub")
149
+ key_path.parent.mkdir(parents=True, exist_ok=True)
150
+ key_path.write_text("\n".join([key["key"] for key in keys]), encoding="utf-8")
151
+ info_lines.append(f"📄 Source: GitHub @{from_github} │ {len(keys)} key(s) → {key_path}")
152
+ program, status_msg = get_add_ssh_key_script(key_path)
153
+
154
+ else:
155
+ console.print(Panel("❌ No key source specified. Use --help for options.", title="[bold red]Error[/bold red]", border_style="red"))
156
+ raise typer.Exit(code=1)
157
+
158
+ combined_info = "\n".join(info_lines + [""] + status_msg.split("\n"))
159
+ console.print(Panel(combined_info, title="[bold blue]🔑 SSH Key Authorization[/bold blue]", border_style="blue"))
160
+
161
+ if program.strip():
162
+ from machineconfig.utils.code import run_shell_script
163
+ run_shell_script(script=program)
164
+
165
+ import machineconfig.scripts.python.helpers.helpers_network.address as helper
166
+ res = helper.select_lan_ipv4(prefer_vpn=False)
167
+ if res is None:
168
+ console.print(Panel("❌ Could not determine local LAN IPv4 address", title="[bold red]Error[/bold red]", border_style="red"))
169
+ raise typer.Exit(code=1)
170
+
171
+ console.print(Panel(f"✅ Complete │ This machine accessible at: [green]{res}[/green]", title="[bold green]SSH Key Authorization[/bold green]", border_style="green", box=box.DOUBLE_EDGE))
172
+
173
+
174
+ if __name__ == "__main__":
175
+ pass