machineconfig 1.97__py3-none-any.whl → 2.1__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 (268) hide show
  1. machineconfig/cluster/cloud_manager.py +22 -29
  2. machineconfig/cluster/data_transfer.py +2 -3
  3. machineconfig/cluster/distribute.py +0 -2
  4. machineconfig/cluster/file_manager.py +4 -5
  5. machineconfig/cluster/job_params.py +1 -4
  6. machineconfig/cluster/loader_runner.py +8 -11
  7. machineconfig/cluster/remote_machine.py +4 -5
  8. machineconfig/cluster/script_execution.py +2 -2
  9. machineconfig/cluster/script_notify_upon_completion.py +0 -1
  10. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +4 -6
  11. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
  12. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +35 -75
  13. machineconfig/cluster/sessions_managers/wt_local.py +113 -185
  14. machineconfig/cluster/sessions_managers/wt_local_manager.py +127 -197
  15. machineconfig/cluster/sessions_managers/wt_remote.py +60 -67
  16. machineconfig/cluster/sessions_managers/wt_remote_manager.py +110 -149
  17. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +61 -64
  18. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +72 -172
  19. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +27 -60
  20. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +58 -137
  21. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +46 -74
  22. machineconfig/cluster/sessions_managers/zellij_local.py +91 -147
  23. machineconfig/cluster/sessions_managers/zellij_local_manager.py +165 -190
  24. machineconfig/cluster/sessions_managers/zellij_remote.py +51 -58
  25. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +40 -46
  26. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +19 -17
  27. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +30 -31
  28. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +64 -134
  29. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +7 -11
  30. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +27 -55
  31. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +14 -13
  32. machineconfig/cluster/templates/cli_click.py +0 -1
  33. machineconfig/cluster/templates/cli_gooey.py +0 -2
  34. machineconfig/cluster/templates/cli_trogon.py +0 -1
  35. machineconfig/cluster/templates/run_cloud.py +0 -1
  36. machineconfig/cluster/templates/run_cluster.py +0 -1
  37. machineconfig/cluster/templates/run_remote.py +0 -1
  38. machineconfig/cluster/templates/utils.py +27 -11
  39. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  40. machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
  41. machineconfig/jobs/python/check_installations.py +9 -9
  42. machineconfig/jobs/python/create_bootable_media.py +0 -2
  43. machineconfig/jobs/python/python_cargo_build_share.py +2 -2
  44. machineconfig/jobs/python/python_ve_symlink.py +9 -11
  45. machineconfig/jobs/python/tasks.py +0 -1
  46. machineconfig/jobs/python/vscode/api.py +5 -5
  47. machineconfig/jobs/python/vscode/link_ve.py +20 -21
  48. machineconfig/jobs/python/vscode/select_interpreter.py +28 -29
  49. machineconfig/jobs/python/vscode/sync_code.py +14 -18
  50. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  51. machineconfig/jobs/python_custom_installers/archive/ngrok.py +15 -15
  52. machineconfig/jobs/python_custom_installers/dev/aider.py +10 -18
  53. machineconfig/jobs/python_custom_installers/dev/alacritty.py +12 -21
  54. machineconfig/jobs/python_custom_installers/dev/brave.py +13 -22
  55. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +13 -20
  56. machineconfig/jobs/python_custom_installers/dev/code.py +17 -24
  57. machineconfig/jobs/python_custom_installers/dev/cursor.py +10 -21
  58. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +12 -11
  59. machineconfig/jobs/python_custom_installers/dev/espanso.py +19 -23
  60. machineconfig/jobs/python_custom_installers/dev/goes.py +9 -16
  61. machineconfig/jobs/python_custom_installers/dev/lvim.py +13 -21
  62. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +15 -22
  63. machineconfig/jobs/python_custom_installers/dev/redis.py +15 -23
  64. machineconfig/jobs/python_custom_installers/dev/wezterm.py +15 -22
  65. machineconfig/jobs/python_custom_installers/dev/winget.py +32 -50
  66. machineconfig/jobs/python_custom_installers/docker.py +15 -24
  67. machineconfig/jobs/python_custom_installers/gh.py +18 -26
  68. machineconfig/jobs/python_custom_installers/hx.py +33 -17
  69. machineconfig/jobs/python_custom_installers/warp-cli.py +15 -23
  70. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  71. machineconfig/jobs/python_generic_installers/config.json +412 -389
  72. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  73. machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
  74. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
  75. machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
  76. machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
  77. machineconfig/jobs/windows/start_terminal.ps1 +1 -1
  78. machineconfig/logger.py +50 -0
  79. machineconfig/profile/create.py +50 -36
  80. machineconfig/profile/create_hardlinks.py +33 -26
  81. machineconfig/profile/shell.py +87 -60
  82. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  83. machineconfig/scripts/cloud/init.sh +2 -2
  84. machineconfig/scripts/linux/checkout_versions +1 -1
  85. machineconfig/scripts/linux/choose_wezterm_theme +1 -1
  86. machineconfig/scripts/linux/cloud_copy +1 -1
  87. machineconfig/scripts/linux/cloud_manager +1 -1
  88. machineconfig/scripts/linux/cloud_mount +1 -1
  89. machineconfig/scripts/linux/cloud_repo_sync +1 -1
  90. machineconfig/scripts/linux/cloud_sync +1 -1
  91. machineconfig/scripts/linux/croshell +1 -1
  92. machineconfig/scripts/linux/devops +3 -5
  93. machineconfig/scripts/linux/fire +2 -1
  94. machineconfig/scripts/linux/fire_agents +3 -3
  95. machineconfig/scripts/linux/ftpx +1 -1
  96. machineconfig/scripts/linux/gh_models +1 -1
  97. machineconfig/scripts/linux/kill_process +1 -1
  98. machineconfig/scripts/linux/mcinit +2 -2
  99. machineconfig/scripts/linux/repos +1 -1
  100. machineconfig/scripts/linux/scheduler +1 -1
  101. machineconfig/scripts/linux/start_slidev +1 -1
  102. machineconfig/scripts/linux/start_terminals +1 -1
  103. machineconfig/scripts/linux/url2md +1 -1
  104. machineconfig/scripts/linux/warp-cli.sh +122 -0
  105. machineconfig/scripts/linux/wifi_conn +1 -1
  106. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  107. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  111. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  112. machineconfig/scripts/python/ai/__init__.py +0 -0
  113. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  114. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  115. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  116. machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
  117. machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
  118. machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
  119. machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
  120. machineconfig/scripts/python/ai/generate_files.py +84 -0
  121. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
  122. machineconfig/scripts/python/ai/mcinit.py +107 -0
  123. machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
  124. machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
  125. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +52 -0
  126. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  127. machineconfig/scripts/python/archive/tmate_start.py +3 -3
  128. machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
  129. machineconfig/scripts/python/cloud_copy.py +20 -19
  130. machineconfig/scripts/python/cloud_mount.py +10 -8
  131. machineconfig/scripts/python/cloud_repo_sync.py +15 -15
  132. machineconfig/scripts/python/cloud_sync.py +1 -1
  133. machineconfig/scripts/python/croshell.py +18 -16
  134. machineconfig/scripts/python/devops.py +6 -6
  135. machineconfig/scripts/python/devops_add_identity.py +9 -7
  136. machineconfig/scripts/python/devops_add_ssh_key.py +19 -19
  137. machineconfig/scripts/python/devops_backup_retrieve.py +14 -14
  138. machineconfig/scripts/python/devops_devapps_install.py +3 -3
  139. machineconfig/scripts/python/devops_update_repos.py +141 -53
  140. machineconfig/scripts/python/dotfile.py +3 -3
  141. machineconfig/scripts/python/fire_agents.py +202 -41
  142. machineconfig/scripts/python/fire_jobs.py +20 -21
  143. machineconfig/scripts/python/ftpx.py +4 -3
  144. machineconfig/scripts/python/gh_models.py +94 -94
  145. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  146. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  147. machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
  148. machineconfig/scripts/python/helpers/helpers2.py +3 -3
  149. machineconfig/scripts/python/helpers/helpers4.py +8 -7
  150. machineconfig/scripts/python/helpers/helpers5.py +7 -7
  151. machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
  152. machineconfig/scripts/python/mount_nfs.py +4 -3
  153. machineconfig/scripts/python/mount_nw_drive.py +4 -4
  154. machineconfig/scripts/python/mount_ssh.py +4 -3
  155. machineconfig/scripts/python/repos.py +9 -9
  156. machineconfig/scripts/python/scheduler.py +1 -1
  157. machineconfig/scripts/python/start_slidev.py +9 -8
  158. machineconfig/scripts/python/start_terminals.py +1 -1
  159. machineconfig/scripts/python/viewer.py +40 -40
  160. machineconfig/scripts/python/wifi_conn.py +65 -66
  161. machineconfig/scripts/python/wsl_windows_transfer.py +2 -2
  162. machineconfig/scripts/windows/checkout_version.ps1 +1 -3
  163. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
  164. machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
  165. machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
  166. machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
  167. machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
  168. machineconfig/scripts/windows/croshell.ps1 +2 -2
  169. machineconfig/scripts/windows/devops.ps1 +1 -4
  170. machineconfig/scripts/windows/dotfile.ps1 +1 -3
  171. machineconfig/scripts/windows/fire.ps1 +1 -1
  172. machineconfig/scripts/windows/ftpx.ps1 +2 -2
  173. machineconfig/scripts/windows/gpt.ps1 +1 -1
  174. machineconfig/scripts/windows/kill_process.ps1 +1 -2
  175. machineconfig/scripts/windows/mcinit.ps1 +2 -2
  176. machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
  177. machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
  178. machineconfig/scripts/windows/pomodoro.ps1 +1 -1
  179. machineconfig/scripts/windows/py2exe.ps1 +1 -3
  180. machineconfig/scripts/windows/repos.ps1 +1 -1
  181. machineconfig/scripts/windows/scheduler.ps1 +1 -1
  182. machineconfig/scripts/windows/snapshot.ps1 +2 -2
  183. machineconfig/scripts/windows/start_slidev.ps1 +1 -1
  184. machineconfig/scripts/windows/start_terminals.ps1 +1 -1
  185. machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
  186. machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
  187. machineconfig/settings/lf/linux/lfrc +1 -1
  188. machineconfig/settings/linters/.ruff.toml +2 -2
  189. machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
  190. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
  191. machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
  192. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
  193. machineconfig/settings/shells/wt/settings.json +8 -8
  194. machineconfig/settings/svim/linux/init.toml +1 -1
  195. machineconfig/settings/svim/windows/init.toml +1 -1
  196. machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -54
  197. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
  198. machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
  199. machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
  200. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
  201. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
  202. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
  203. machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
  204. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +75 -18
  205. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +52 -42
  206. machineconfig/utils/ai/browser_user_wrapper.py +5 -5
  207. machineconfig/utils/ai/generate_file_checklist.py +19 -22
  208. machineconfig/utils/ai/url2md.py +5 -3
  209. machineconfig/utils/cloud/onedrive/setup_oauth.py +5 -4
  210. machineconfig/utils/cloud/onedrive/transaction.py +192 -227
  211. machineconfig/utils/code.py +71 -43
  212. machineconfig/utils/installer.py +77 -85
  213. machineconfig/utils/installer_utils/installer_abc.py +29 -17
  214. machineconfig/utils/installer_utils/installer_class.py +188 -83
  215. machineconfig/utils/io_save.py +3 -15
  216. machineconfig/utils/links.py +22 -11
  217. machineconfig/utils/notifications.py +197 -0
  218. machineconfig/utils/options.py +38 -25
  219. machineconfig/utils/path.py +18 -6
  220. machineconfig/utils/path_reduced.py +637 -316
  221. machineconfig/utils/procs.py +69 -63
  222. machineconfig/utils/scheduling.py +11 -13
  223. machineconfig/utils/ssh.py +351 -0
  224. machineconfig/utils/terminal.py +225 -0
  225. machineconfig/utils/utils.py +13 -12
  226. machineconfig/utils/utils2.py +43 -10
  227. machineconfig/utils/utils5.py +242 -46
  228. machineconfig/utils/ve.py +11 -6
  229. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/METADATA +15 -9
  230. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/RECORD +232 -235
  231. machineconfig/cluster/self_ssh.py +0 -57
  232. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  233. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  234. machineconfig/jobs/python/archive/python_tools.txt +0 -12
  235. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  236. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  237. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  238. machineconfig/jobs/python_generic_installers/update.py +0 -3
  239. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  240. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  241. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  242. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  243. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  244. machineconfig/scripts/linux/activate_ve +0 -87
  245. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  246. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  247. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  248. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  249. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  250. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  251. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  252. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  253. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  254. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  255. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  256. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  257. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  258. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  259. machineconfig/scripts/python/ai/init.py +0 -56
  260. machineconfig/scripts/python/ai/rules/python/dev.md +0 -31
  261. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  262. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  263. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  264. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  265. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  266. machineconfig/scripts/windows/activate_ve.ps1 +0 -54
  267. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/WHEEL +0 -0
  268. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,197 @@
1
+
2
+ """Notifications Module
3
+ """
4
+
5
+ # from crocodile.core import install_n_import
6
+ # from crocodile.file_management import P, Read
7
+ from pathlib import Path
8
+ # from crocodile.meta import RepeatUntilNoException
9
+ import smtplib
10
+ import imaplib
11
+ # from email import message
12
+ # from email import encoders
13
+ # from email.mime.base import MIMEBase
14
+ from email.mime.text import MIMEText
15
+ from email.mime.multipart import MIMEMultipart
16
+ from typing import Optional, Any, Union, Literal
17
+ from markdown import markdown
18
+
19
+
20
+
21
+ def download_to_memory(path: Path, allow_redirects: bool = True, timeout: Optional[float] = None, params: Any = None) -> 'Any':
22
+ import requests
23
+ return requests.get(path.as_posix().replace("https:/", "https://").replace("http:/", "http://"), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
24
+
25
+
26
+ def get_github_markdown_css() -> str:
27
+ pp = r'https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown-dark.css'
28
+ return download_to_memory(Path(pp)).text
29
+
30
+
31
+ def md2html(body: str):
32
+ gh_style = Path(__file__).parent.joinpath("gh_style.css").read_text()
33
+ return f"""
34
+ <!DOCTYPE html>
35
+ <html>
36
+ <meta name="viewport" content="width=device-width, initial-scale=1">
37
+ <style>
38
+ {gh_style}
39
+ .markdown-body {{
40
+ box-sizing: border-box;
41
+ min-width: 200px;
42
+ max-width: 1350px;
43
+ margin: 0 auto;
44
+ padding: 45px;
45
+ line-height: 1.8;
46
+ }}
47
+ @media (max-width: 767px) {{.markdown-body {{padding: 15px;}}
48
+ }}
49
+ </style>
50
+ <body>
51
+ <div class="markdown-body">
52
+ {markdown(body)}
53
+ </div>
54
+ </body>
55
+ </html>"""
56
+
57
+
58
+ class Email:
59
+ @staticmethod
60
+ def get_source_of_truth():
61
+ path = Path.home().joinpath("dotfiles/machineconfig/emails.ini")
62
+ if not path.exists():
63
+ raise FileNotFoundError(f"""File not found: {path}. It should be an ini file with this structure
64
+ [resend]
65
+ api_key = xxx
66
+
67
+ [config1]
68
+ email_add = a@b.com
69
+ password = 123
70
+ smtp_host = a@b.com
71
+ smtp_port = 465
72
+ imap_host = b@c.com
73
+ imap_port = 465
74
+ encryption = ssl
75
+
76
+ """)
77
+
78
+ if not Path(path).exists() or Path(path).is_dir(): raise FileNotFoundError(f"File not found or is a directory: {path}")
79
+ import configparser
80
+ res = configparser.ConfigParser()
81
+ res.read(filenames=[str(path)], encoding=None)
82
+ return res
83
+
84
+ def __init__(self, config: dict[str, Any]):
85
+ self.config = config
86
+ from smtplib import SMTP_SSL, SMTP
87
+ self.server: Union[SMTP_SSL, SMTP]
88
+ if config['encryption'].lower() == "ssl": self.server = smtplib.SMTP_SSL(host=self.config["smtp_host"], port=self.config["smtp_port"])
89
+ elif config['encryption'].lower() == "tls": self.server = smtplib.SMTP(host=self.config["smtp_host"], port=self.config["smtp_port"])
90
+ self.server.login(self.config['email_add'], password=self.config["password"])
91
+
92
+ def send_message(self, to: str, subject: str, body: str, txt_to_html: bool = True, attachments: Optional[list[Any]] = None):
93
+ _ = attachments
94
+ body += "\n\nThis is an automated email sent via crocodile.comms script."
95
+ # msg = message.EmailMessage()
96
+ msg = MIMEMultipart("alternative")
97
+ msg["subject"] = subject
98
+ msg["From"] = self.config['email_add']
99
+ msg["To"] = to
100
+ # msg['Content-Type'] = "text/html"
101
+ # msg.set_content(body)
102
+
103
+ # <link rel="stylesheet" href="github-markdown.css">
104
+ # <link type="text/css" rel="stylesheet" href="https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown-dark.css" />
105
+
106
+ if txt_to_html: body = md2html(body=body)
107
+ msg.attach(MIMEText(body, "html"))
108
+ # if attachments is None: attachments = [] # see: https://fedingo.com/how-to-send-html-mail-with-attachment-using-python/
109
+ # for attachment in attachmenthrs: msg.attach(attachment.read_bytes(), filename=attachment.stem, maintype="image", subtype=attachment.suffix)
110
+ # for attachment in attachments: msg.attach(attachment.read_bytes(), filename=attachment.stem, maintype="application", subtype="octet-stream")
111
+
112
+ self.server.send_message(msg)
113
+
114
+ @staticmethod
115
+ def manage_folders(email_add: str, pwd: str):
116
+ server = imaplib.IMAP4()
117
+ server.starttls()
118
+ server.login(email_add, password=pwd)
119
+
120
+ def send_email(self, to_addrs: str, msg: str): return self.server.sendmail(from_addr=self.config['email_add'], to_addrs=to_addrs, msg=msg)
121
+ def close(self): self.server.quit() # Closing is vital as many servers do not allow mutiple connections.
122
+
123
+ @staticmethod
124
+ def send_and_close(config_name: Optional[str], to: str, subject: str, body: str) -> Any:
125
+ """If config_name is None, it sends from a generic email address."""
126
+ if config_name is None:
127
+ raise NotImplementedError("Sending email without a config_name is not implemented. You need to create an emails.ini file in ~/dotfiles/machineconfig/ with your email configuration. See the docstring of the get_source_of_truth method for more information.")
128
+ # config = Email.get_source_of_truth()
129
+ # try:
130
+ # api_key = config['resend']['api_key']
131
+ # to = config["resend"]["signup_email"]
132
+ # except KeyError as ke:
133
+ # msggg = "You did not pass a config_name, therefore, the default is to use resend, however, you need to add your resend api key to the emails.ini file."
134
+ # raise KeyError(msggg) from ke
135
+
136
+ # _resend = install_n_import("resend")
137
+ # import resend # type: ignore
138
+ # resend.api_key = api_key
139
+ # r = resend.Emails.send({
140
+ # "from": "onboarding@resend.dev",
141
+ # "to": to,
142
+ # "subject": subject,
143
+ # "html": md2html(body=body)
144
+ # })
145
+ # return r
146
+
147
+ else:
148
+ config = dict(Email.get_source_of_truth()[config_name])
149
+ tmp = Email(config=config)
150
+ tmp.send_message(to=to, subject=subject, body=body)
151
+ tmp.close()
152
+
153
+ @staticmethod
154
+ def send_m365(to: list[str], subject: str, body: Optional[str], body_file: Optional[str], body_content_type: Literal["HTML", "Text"], attachments: Optional[list[Path]] = None) -> None:
155
+ if body_file is not None:
156
+ assert body is None, "You cannot pass both body and body_file."
157
+ body_file_path = Path(body_file)
158
+ assert body_file_path.exists(), f"File not found: {body_file_path}"
159
+ else:
160
+ body_file_path = None
161
+ assert body is not None, "You must pass either body or body_file."
162
+ from crocodile.meta import Terminal
163
+ to_str = ",".join(to)
164
+ attachments_str = " ".join([f"--attachment {str(p)}" for p in attachments]) if attachments is not None else ""
165
+
166
+ if body_file is not None:
167
+ body_arg = f"--bodyContents @{body_file_path}"
168
+ else:
169
+ body_arg = f'"{body}"'
170
+ cmd = f"""m365 outlook mail send --verbose --saveToSentItems --importance normal --bodyContentType {body_content_type} --bodyContents {body_arg} --subject "{subject}" --to {to_str} {attachments_str}"""
171
+ response = Terminal().run(cmd, shell="powershell")
172
+ response.print(desc="Email sending response")
173
+
174
+
175
+ # class PhoneNotification: # security concerns: avoid using this.
176
+ # def __init__(self, token: Optional[str]):
177
+ # if token is None:
178
+ # path = P.home().joinpath("dotfiles/machineconfig/phone_notification.ini")
179
+ # ini = Read.ini(path)
180
+ # token_ = ini["default"]["token"]
181
+ # else:
182
+ # token_ = token
183
+ # pushbullet = install_n_import("pushbullet")
184
+ # self.api = pushbullet.Pushbullet(token_)
185
+ # def send_notification(self, title: str = "Note From Python", body: str = "A notfication"):
186
+ # self.api.push_note(title=title, body=body)
187
+ # @staticmethod
188
+ # def open_website():
189
+ # P(r"https://www.pushbullet.com/")()
190
+ # @staticmethod # https://www.youtube.com/watch?v=tbzPcKRZlHg
191
+ # def try_me(bulletpoint_token: str):
192
+ # n = PhoneNotification(bulletpoint_token)
193
+ # n.send_notification()
194
+
195
+
196
+ if __name__ == '__main__':
197
+ pass
@@ -1,4 +1,3 @@
1
- from crocodile.meta import Terminal
2
1
  from pathlib import Path
3
2
  from rich.text import Text
4
3
  from rich.panel import Panel
@@ -10,47 +9,50 @@ from typing import Optional, Union, TypeVar, Iterable
10
9
  T = TypeVar("T")
11
10
 
12
11
 
13
-
14
12
  def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> bool:
15
13
  if platform.system() == "Windows":
16
14
  tool_name = tool_name.replace(".exe", "") + ".exe"
17
15
 
18
- if platform.system() == "Windows": cmd = "where.exe"
19
- elif platform.system() in ["Linux", "Darwin"]: cmd = "which"
20
- else: raise NotImplementedError(f"platform {platform.system()} not implemented")
16
+ if platform.system() == "Windows":
17
+ cmd = "where.exe"
18
+ elif platform.system() in ["Linux", "Darwin"]:
19
+ cmd = "which"
20
+ else:
21
+ raise NotImplementedError(f"platform {platform.system()} not implemented")
21
22
 
22
23
  try:
23
24
  _tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
24
- res: bool=True
25
+ res: bool = True
25
26
  except (subprocess.CalledProcessError, FileNotFoundError):
26
27
  res = False
27
28
  if res is False and install_script is not None:
28
29
  console = Console()
29
30
  console.print(Panel(f"📥 INSTALLING TOOL | Installing {tool_name}...", border_style="bold blue", expand=False))
30
- Terminal().run(install_script, shell="powershell").print()
31
+ result = subprocess.run(install_script, shell=True, capture_output=True, text=True)
32
+ print(f"Command: {install_script}")
33
+ if result.stdout:
34
+ print(f"STDOUT: {result.stdout}")
35
+ if result.stderr:
36
+ print(f"STDERR: {result.stderr}")
37
+ print(f"Return code: {result.returncode}")
31
38
  return check_tool_exists(tool_name=tool_name, install_script=None)
32
39
  return res
33
40
 
34
41
 
35
- def choose_one_option(options: Iterable[T], header: str="", tail: str="", prompt: str="", msg: str="",
36
- default: Optional[T] = None, fzf: bool=False, custom_input: bool=False) -> T:
37
- choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
38
- default=default, fzf=fzf, multi=False, custom_input=custom_input)
42
+ def choose_one_option(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
43
+ choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=False, custom_input=custom_input)
39
44
  assert not isinstance(choice_key, list)
40
45
  return choice_key
41
46
 
42
47
 
43
- def choose_multiple_options(options: Iterable[T], header: str="", tail: str="", prompt: str="", msg: str="",
44
- default: Optional[T] = None, custom_input: bool=False) -> list[T]:
45
- choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
46
- default=default, fzf=True, multi=True,
47
- custom_input=custom_input)
48
- if isinstance(choice_key, list): return choice_key
48
+ def choose_multiple_options(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, custom_input: bool = False) -> list[T]:
49
+ choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=True, multi=True, custom_input=custom_input)
50
+ if isinstance(choice_key, list):
51
+ return choice_key
49
52
  return [choice_key]
50
53
 
51
54
 
52
- def display_options(msg: str, options: Iterable[T], header: str="", tail: str="", prompt: str="",
53
- default: Optional[T] = None, fzf: bool=False, multi: bool=False, custom_input: bool=False) -> Union[T, list[T]]:
55
+ def display_options(msg: str, options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, multi: bool = False, custom_input: bool = False) -> Union[T, list[T]]:
54
56
  # TODO: replace with https://github.com/tmbo/questionary
55
57
  # # also see https://github.com/charmbracelet/gum
56
58
  tool_name = "fzf"
@@ -59,6 +61,7 @@ def display_options(msg: str, options: Iterable[T], header: str="", tail: str=""
59
61
  console = Console()
60
62
  if fzf and check_tool_exists(tool_name):
61
63
  from pyfzf.pyfzf import FzfPrompt
64
+
62
65
  fzf_prompt = FzfPrompt()
63
66
  nl = "\n"
64
67
  choice_string_multi: list[str] = fzf_prompt.prompt(choices=options_strings, fzf_options=("--multi" if multi else "") + f' --prompt "{prompt.replace(nl, " ")}" ') # --border-label={msg.replace(nl, ' ')}")
@@ -78,7 +81,8 @@ def display_options(msg: str, options: Iterable[T], header: str="", tail: str=""
78
81
  if default is not None:
79
82
  assert default in options, f"Default `{default}` option not in options `{list(options)}`"
80
83
  default_msg = Text(" <<<<-------- DEFAULT", style="bold red")
81
- else: default_msg = Text("")
84
+ else:
85
+ default_msg = Text("")
82
86
  txt = Text("\n" + msg + "\n")
83
87
  for idx, key in enumerate(options):
84
88
  txt = txt + Text(f"{idx:2d} ", style="bold blue") + str(key) + (default_msg if default is not None and default == key else "") + "\n"
@@ -105,7 +109,8 @@ def display_options(msg: str, options: Iterable[T], header: str="", tail: str=""
105
109
  if choice_string in options_strings: # string input
106
110
  choice_idx = options_strings.index(choice_one) # type: ignore
107
111
  choice_one = list(options)[choice_idx]
108
- elif custom_input: return str(choice_string) # type: ignore
112
+ elif custom_input:
113
+ return str(choice_string) # type: ignore
109
114
  else:
110
115
  _ = ie
111
116
  # raise ValueError(f"Unknown choice. {choice_string}") from ie
@@ -123,27 +128,35 @@ def display_options(msg: str, options: Iterable[T], header: str="", tail: str=""
123
128
  console.print(Panel(f"❓ Unknown choice: '{choice_string}'", title="Error", expand=False))
124
129
  return display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=multi, custom_input=custom_input)
125
130
  console.print(Panel(f"✅ Selected option {choice_idx}: {choice_one}", title="Selected", expand=False))
126
- if multi: return [choice_one]
131
+ if multi:
132
+ return [choice_one]
127
133
  return choice_one
128
134
 
129
135
 
130
136
  def choose_cloud_interactively() -> str:
131
137
  console = Console()
132
138
  console.print(Panel("🔍 LISTING CLOUD REMOTES | Fetching available cloud remotes...", border_style="bold blue", expand=False))
133
- tmp = Terminal().run("rclone listremotes").op_if_successfull_or_default(strict_returcode=False)
139
+ result = subprocess.run("rclone listremotes", shell=True, capture_output=True, text=True)
140
+ tmp = result.stdout if result.returncode == 0 else None
134
141
  if isinstance(tmp, str):
135
142
  remotes: list[str] = [x.replace(":", "") for x in tmp.splitlines()]
136
143
 
137
- else: raise ValueError(f"Got {tmp} from rclone listremotes")
144
+ else:
145
+ raise ValueError(f"Got {tmp} from rclone listremotes")
138
146
  if len(remotes) == 0:
139
147
  raise RuntimeError("You don't have remotes. Configure your rclone first to get cloud services access.")
140
148
  cloud: str = choose_one_option(msg="WHICH CLOUD?", options=list(remotes), default=remotes[0], fzf=True)
141
149
  console.print(Panel(f"✅ SELECTED CLOUD | {cloud}", border_style="bold blue", expand=False))
142
150
  return cloud
143
151
 
152
+
144
153
  def get_ssh_hosts() -> list[str]:
145
154
  from paramiko import SSHConfig
155
+
146
156
  c = SSHConfig()
147
157
  c.parse(open(Path.home().joinpath(".ssh/config"), encoding="utf-8"))
148
158
  return list(c.get_hostnames())
149
- def choose_ssh_host(multi: bool=True): return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
159
+
160
+
161
+ def choose_ssh_host(multi: bool = True):
162
+ return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.path_reduced import P as PathExtended
1
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
2
  from machineconfig.utils.options import choose_one_option
3
3
  from rich.console import Console
4
4
  from rich.panel import Panel
@@ -11,8 +11,15 @@ from pathlib import Path
11
11
  T = TypeVar("T")
12
12
  console = Console()
13
13
 
14
+
14
15
  def sanitize_path(a_path: PathExtended) -> PathExtended:
15
16
  path = PathExtended(a_path)
17
+ if Path.cwd() == Path.home() and not path.exists():
18
+ result = input("Current working directory is home, and passed path is not full path, are you sure you want to continue, [y]/n? ") or "y"
19
+ if result == "y":
20
+ import sys
21
+
22
+ sys.exit()
16
23
  if path.as_posix().startswith("/home") or path.as_posix().startswith("/Users"):
17
24
  if platform.system() == "Windows": # path copied from Linux/Mac to Windows
18
25
  # For Linux: /home/username, for Mac: /Users/username
@@ -65,7 +72,8 @@ def match_file_name(sub_string: str, search_root: PathExtended) -> PathExtended:
65
72
  search_root_obj = search_root.absolute()
66
73
  # assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
67
74
  filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string)
68
- if len(filename_matches) == 1: return PathExtended(filename_matches[0])
75
+ if len(filename_matches) == 1:
76
+ return PathExtended(filename_matches[0])
69
77
  console.print(Panel(f"Partial filename match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
70
78
  if len(filename_matches) < 10:
71
79
  print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
@@ -73,7 +81,8 @@ def match_file_name(sub_string: str, search_root: PathExtended) -> PathExtended:
73
81
  print("Try to narrow down filename_matches search by case-sensitivity.")
74
82
  # let's see if avoiding .lower() helps narrowing down to one result
75
83
  reduced_scripts = [a_potential_match for a_potential_match in filename_matches if sub_string in a_potential_match.name]
76
- if len(reduced_scripts) == 1: return PathExtended(reduced_scripts[0])
84
+ if len(reduced_scripts) == 1:
85
+ return PathExtended(reduced_scripts[0])
77
86
  elif len(reduced_scripts) > 1:
78
87
  choice = choose_one_option(msg="Multiple matches found", options=reduced_scripts, fzf=True)
79
88
  return PathExtended(choice)
@@ -87,18 +96,21 @@ def match_file_name(sub_string: str, search_root: PathExtended) -> PathExtended:
87
96
  elif len(partial_path_matches) > 1:
88
97
  print("Try to narrow down partial_path_matches search by case-sensitivity.")
89
98
  reduced_scripts = [a_potential_match for a_potential_match in partial_path_matches if sub_string in a_potential_match.as_posix()]
90
- if len(reduced_scripts) == 1: return PathExtended(reduced_scripts[0])
99
+ if len(reduced_scripts) == 1:
100
+ return PathExtended(reduced_scripts[0])
91
101
  print(f"Result: This still generated {len(reduced_scripts)} results.")
92
102
  try:
93
103
  fzf_cmd = f"cd '{search_root_obj}'; fd --type file --strip-cwd-prefix | fzf --ignore-case --exact --query={sub_string}"
94
104
  console.print(Panel(f"🔍 Second attempt: SEARCH STRATEGY | Using fd to search for '{sub_string}' in '{search_root_obj}' ...\n{fzf_cmd}", title="Search Strategy", expand=False))
95
- search_res_raw = subprocess.run(fzf_cmd, stdout=subprocess.PIPE, text=True, check=True, shell=True,).stdout
105
+ search_res_raw = subprocess.run(fzf_cmd, stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout
96
106
  search_res = search_res_raw.strip().split("\\n")[:-1]
97
107
  except subprocess.CalledProcessError as cpe:
98
108
  console.print(Panel(f"❌ ERROR | FZF search failed with '{sub_string}' in '{search_root_obj}'.\n{cpe}", title="Error", expand=False))
99
109
  import sys
110
+
100
111
  sys.exit(f"💥 FILE NOT FOUND | Path {sub_string} does not exist @ root {search_root_obj}. No search results.")
101
- if len(search_res) == 1: return search_root_obj.joinpath(search_res_raw)
112
+ if len(search_res) == 1:
113
+ return search_root_obj.joinpath(search_res_raw)
102
114
 
103
115
  print(f"⚠️ WARNING | Multiple search results found for `{sub_string}`\n'{search_res_raw}'")
104
116
  cmd = f"cd '{search_root_obj}'; fd --type file | fzf --select-1 --query={sub_string}"