machineconfig 2.0__py3-none-any.whl → 2.2__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 (253) hide show
  1. machineconfig/cluster/cloud_manager.py +0 -3
  2. machineconfig/cluster/data_transfer.py +0 -1
  3. machineconfig/cluster/file_manager.py +0 -1
  4. machineconfig/cluster/job_params.py +0 -3
  5. machineconfig/cluster/loader_runner.py +0 -3
  6. machineconfig/cluster/remote_machine.py +0 -1
  7. machineconfig/cluster/script_notify_upon_completion.py +0 -1
  8. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +5 -6
  9. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
  10. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +17 -57
  11. machineconfig/cluster/sessions_managers/wt_local.py +36 -110
  12. machineconfig/cluster/sessions_managers/wt_local_manager.py +42 -112
  13. machineconfig/cluster/sessions_managers/wt_remote.py +23 -30
  14. machineconfig/cluster/sessions_managers/wt_remote_manager.py +20 -62
  15. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +10 -15
  16. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +27 -127
  17. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +10 -43
  18. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +22 -101
  19. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +11 -39
  20. machineconfig/cluster/sessions_managers/zellij_local.py +49 -102
  21. machineconfig/cluster/sessions_managers/zellij_local_manager.py +34 -78
  22. machineconfig/cluster/sessions_managers/zellij_remote.py +17 -24
  23. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +7 -13
  24. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -2
  25. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +6 -6
  26. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +18 -88
  27. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +2 -6
  28. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +12 -40
  29. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -2
  30. machineconfig/cluster/templates/cli_click.py +0 -1
  31. machineconfig/cluster/templates/cli_gooey.py +0 -2
  32. machineconfig/cluster/templates/cli_trogon.py +0 -1
  33. machineconfig/cluster/templates/run_cloud.py +0 -1
  34. machineconfig/cluster/templates/run_cluster.py +0 -1
  35. machineconfig/cluster/templates/run_remote.py +0 -1
  36. machineconfig/cluster/templates/utils.py +27 -46
  37. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  38. machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
  39. machineconfig/jobs/python/check_installations.py +2 -1
  40. machineconfig/jobs/python/create_bootable_media.py +0 -2
  41. machineconfig/jobs/python/python_ve_symlink.py +9 -11
  42. machineconfig/jobs/python/tasks.py +0 -1
  43. machineconfig/jobs/python/vscode/api.py +5 -5
  44. machineconfig/jobs/python/vscode/link_ve.py +13 -14
  45. machineconfig/jobs/python/vscode/select_interpreter.py +21 -22
  46. machineconfig/jobs/python/vscode/sync_code.py +9 -13
  47. machineconfig/jobs/python_custom_installers/archive/ngrok.py +13 -13
  48. machineconfig/jobs/python_custom_installers/dev/aider.py +7 -15
  49. machineconfig/jobs/python_custom_installers/dev/alacritty.py +9 -18
  50. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -19
  51. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +8 -15
  52. machineconfig/jobs/python_custom_installers/dev/code.py +12 -32
  53. machineconfig/jobs/python_custom_installers/dev/cursor.py +3 -14
  54. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +8 -7
  55. machineconfig/jobs/python_custom_installers/dev/espanso.py +15 -19
  56. machineconfig/jobs/python_custom_installers/dev/goes.py +5 -12
  57. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -17
  58. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +12 -19
  59. machineconfig/jobs/python_custom_installers/dev/redis.py +12 -20
  60. machineconfig/jobs/python_custom_installers/dev/wezterm.py +12 -19
  61. machineconfig/jobs/python_custom_installers/dev/winget.py +5 -23
  62. machineconfig/jobs/python_custom_installers/docker.py +12 -21
  63. machineconfig/jobs/python_custom_installers/gh.py +11 -19
  64. machineconfig/jobs/python_custom_installers/hx.py +32 -16
  65. machineconfig/jobs/python_custom_installers/warp-cli.py +12 -20
  66. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  67. machineconfig/jobs/python_generic_installers/config.json +1 -1
  68. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  69. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
  70. machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
  71. machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
  72. machineconfig/jobs/windows/start_terminal.ps1 +1 -1
  73. machineconfig/profile/create.py +38 -26
  74. machineconfig/profile/create_hardlinks.py +29 -20
  75. machineconfig/profile/shell.py +56 -32
  76. machineconfig/scripts/__init__.py +0 -2
  77. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  78. machineconfig/scripts/cloud/init.sh +2 -2
  79. machineconfig/scripts/linux/checkout_versions +1 -1
  80. machineconfig/scripts/linux/choose_wezterm_theme +1 -1
  81. machineconfig/scripts/linux/cloud_copy +1 -1
  82. machineconfig/scripts/linux/cloud_manager +1 -1
  83. machineconfig/scripts/linux/cloud_mount +1 -1
  84. machineconfig/scripts/linux/cloud_repo_sync +1 -1
  85. machineconfig/scripts/linux/cloud_sync +1 -1
  86. machineconfig/scripts/linux/croshell +1 -1
  87. machineconfig/scripts/linux/devops +7 -7
  88. machineconfig/scripts/linux/fire +1 -1
  89. machineconfig/scripts/linux/fire_agents +3 -2
  90. machineconfig/scripts/linux/ftpx +1 -1
  91. machineconfig/scripts/linux/gh_models +1 -1
  92. machineconfig/scripts/linux/kill_process +1 -1
  93. machineconfig/scripts/linux/mcinit +1 -1
  94. machineconfig/scripts/linux/repos +1 -1
  95. machineconfig/scripts/linux/scheduler +1 -1
  96. machineconfig/scripts/linux/start_slidev +1 -1
  97. machineconfig/scripts/linux/start_terminals +1 -1
  98. machineconfig/scripts/linux/url2md +1 -1
  99. machineconfig/scripts/linux/warp-cli.sh +122 -0
  100. machineconfig/scripts/linux/wifi_conn +1 -1
  101. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/ai/__init__.py +0 -0
  107. machineconfig/scripts/python/ai/generate_files.py +83 -0
  108. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +2 -2
  109. machineconfig/scripts/python/ai/mcinit.py +14 -7
  110. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +10 -5
  111. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  112. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  113. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  114. machineconfig/scripts/python/cloud_copy.py +23 -14
  115. machineconfig/scripts/python/cloud_mount.py +36 -24
  116. machineconfig/scripts/python/cloud_repo_sync.py +40 -27
  117. machineconfig/scripts/python/cloud_sync.py +4 -4
  118. machineconfig/scripts/python/croshell.py +40 -29
  119. machineconfig/scripts/python/devops.py +45 -27
  120. machineconfig/scripts/python/devops_add_identity.py +15 -25
  121. machineconfig/scripts/python/devops_add_ssh_key.py +8 -8
  122. machineconfig/scripts/python/devops_backup_retrieve.py +18 -16
  123. machineconfig/scripts/python/devops_devapps_install.py +25 -20
  124. machineconfig/scripts/python/devops_update_repos.py +232 -59
  125. machineconfig/scripts/python/dotfile.py +17 -15
  126. machineconfig/scripts/python/fire_agents.py +48 -22
  127. machineconfig/scripts/python/fire_jobs.py +93 -58
  128. machineconfig/scripts/python/ftpx.py +26 -15
  129. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  130. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  131. machineconfig/scripts/python/helpers/helpers2.py +27 -16
  132. machineconfig/scripts/python/helpers/helpers4.py +45 -32
  133. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  134. machineconfig/scripts/python/helpers/repo_sync_helpers.py +32 -10
  135. machineconfig/scripts/python/mount_nfs.py +9 -16
  136. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  137. machineconfig/scripts/python/mount_ssh.py +9 -7
  138. machineconfig/scripts/python/repos.py +216 -58
  139. machineconfig/scripts/python/snapshot.py +0 -1
  140. machineconfig/scripts/python/start_slidev.py +11 -6
  141. machineconfig/scripts/python/start_terminals.py +22 -16
  142. machineconfig/scripts/python/viewer_template.py +0 -1
  143. machineconfig/scripts/python/wifi_conn.py +49 -75
  144. machineconfig/scripts/python/wsl_windows_transfer.py +9 -7
  145. machineconfig/scripts/windows/checkout_version.ps1 +1 -3
  146. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
  147. machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
  148. machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
  149. machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
  150. machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
  151. machineconfig/scripts/windows/croshell.ps1 +2 -2
  152. machineconfig/scripts/windows/devops.ps1 +1 -4
  153. machineconfig/scripts/windows/dotfile.ps1 +1 -3
  154. machineconfig/scripts/windows/fire.ps1 +1 -1
  155. machineconfig/scripts/windows/ftpx.ps1 +2 -2
  156. machineconfig/scripts/windows/gpt.ps1 +1 -1
  157. machineconfig/scripts/windows/kill_process.ps1 +1 -2
  158. machineconfig/scripts/windows/mcinit.ps1 +1 -1
  159. machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
  160. machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
  161. machineconfig/scripts/windows/pomodoro.ps1 +1 -1
  162. machineconfig/scripts/windows/py2exe.ps1 +1 -3
  163. machineconfig/scripts/windows/repos.ps1 +1 -1
  164. machineconfig/scripts/windows/scheduler.ps1 +1 -1
  165. machineconfig/scripts/windows/snapshot.ps1 +2 -2
  166. machineconfig/scripts/windows/start_slidev.ps1 +1 -1
  167. machineconfig/scripts/windows/start_terminals.ps1 +1 -1
  168. machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
  169. machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
  170. machineconfig/settings/lf/linux/lfrc +2 -1
  171. machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
  172. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
  173. machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
  174. machineconfig/settings/svim/linux/init.toml +1 -1
  175. machineconfig/settings/svim/windows/init.toml +1 -1
  176. machineconfig/setup_linux/web_shortcuts/croshell.sh +3 -52
  177. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
  178. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  179. machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
  180. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
  181. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
  182. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
  183. machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
  184. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +58 -13
  185. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +45 -37
  186. machineconfig/utils/ai/generate_file_checklist.py +8 -10
  187. machineconfig/utils/ai/url2md.py +4 -2
  188. machineconfig/utils/cloud/onedrive/setup_oauth.py +1 -0
  189. machineconfig/utils/cloud/onedrive/transaction.py +63 -98
  190. machineconfig/utils/code.py +62 -41
  191. machineconfig/utils/installer.py +29 -35
  192. machineconfig/utils/installer_utils/installer_abc.py +11 -11
  193. machineconfig/utils/installer_utils/installer_class.py +155 -74
  194. machineconfig/utils/links.py +112 -31
  195. machineconfig/utils/notifications.py +211 -0
  196. machineconfig/utils/options.py +41 -42
  197. machineconfig/utils/path.py +13 -6
  198. machineconfig/utils/path_reduced.py +614 -311
  199. machineconfig/utils/procs.py +48 -42
  200. machineconfig/utils/scheduling.py +0 -1
  201. machineconfig/utils/source_of_truth.py +27 -0
  202. machineconfig/utils/ssh.py +146 -85
  203. machineconfig/utils/terminal.py +84 -37
  204. machineconfig/utils/upgrade_packages.py +91 -0
  205. machineconfig/utils/utils2.py +39 -50
  206. machineconfig/utils/utils5.py +195 -116
  207. machineconfig/utils/ve.py +13 -5
  208. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/METADATA +14 -13
  209. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/RECORD +212 -237
  210. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  211. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  212. machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
  213. machineconfig/jobs/python/archive/python_tools.txt +0 -12
  214. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  215. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  216. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  217. machineconfig/jobs/python_generic_installers/update.py +0 -3
  218. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  219. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  220. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  221. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  222. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  223. machineconfig/scripts/linux/activate_ve +0 -87
  224. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  225. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  226. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  227. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  228. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  229. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  230. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  231. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  232. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  233. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  234. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  235. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  236. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  237. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  238. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  239. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
  240. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  241. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  242. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  243. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  244. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  245. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  246. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  247. machineconfig/scripts/windows/activate_ve.ps1 +0 -54
  248. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  249. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  250. machineconfig/utils/utils.py +0 -95
  251. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  252. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
  253. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,220 @@
1
- """Update repositories with fancy output
2
- """
1
+ """Update repositories with fancy output"""
3
2
 
4
- from machineconfig.utils.path_reduced import P as PathExtended
5
- from machineconfig.utils.utils import DEFAULTS_PATH
3
+ import git
4
+ import subprocess
5
+ import hashlib
6
+ from pathlib import Path
7
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
8
+ from machineconfig.utils.source_of_truth import DEFAULTS_PATH
6
9
  from machineconfig.utils.utils2 import read_ini
7
- from platform import system
8
10
 
9
11
 
10
- sep = "\n"
12
+ def get_file_hash(file_path: Path) -> str | None:
13
+ """Get SHA256 hash of a file, return None if file doesn't exist."""
14
+ if not file_path.exists():
15
+ return None
16
+ return hashlib.sha256(file_path.read_bytes()).hexdigest()
11
17
 
12
18
 
13
- def main(verbose: bool=True) -> str:
19
+ def set_permissions_recursive(path: Path, executable: bool = True) -> None:
20
+ """Set permissions recursively for a directory."""
21
+ if not path.exists():
22
+ return
23
+
24
+ if path.is_file():
25
+ if executable:
26
+ path.chmod(0o755)
27
+ else:
28
+ path.chmod(0o644)
29
+ elif path.is_dir():
30
+ path.chmod(0o755)
31
+ for item in path.rglob("*"):
32
+ set_permissions_recursive(item, executable)
33
+
34
+
35
+ def run_uv_sync(repo_path: Path) -> bool:
36
+ """Run uv sync in the given repository path. Returns True if successful."""
37
+ try:
38
+ print(f"🔄 Running uv sync in {repo_path}")
39
+ result = subprocess.run(["uv", "sync"], cwd=repo_path, capture_output=True, text=True, check=True)
40
+ print("✅ uv sync completed successfully")
41
+ if result.stdout:
42
+ print(f"📝 Output: {result.stdout}")
43
+ return True
44
+ except subprocess.CalledProcessError as e:
45
+ print(f"❌ uv sync failed: {e}")
46
+ if e.stderr:
47
+ print(f"📝 Error: {e.stderr}")
48
+ return False
49
+ except FileNotFoundError:
50
+ print("⚠️ uv command not found. Please install uv first.")
51
+ return False
52
+
53
+
54
+ def update_repository(repo: git.Repo, auto_sync: bool = True, allow_password_prompt: bool = False) -> bool:
55
+ """Update a single repository and return True if pyproject.toml or uv.lock changed."""
56
+ repo_path = Path(repo.working_dir)
57
+ print(f"🔄 {'Updating ' + str(repo_path):.^80}")
58
+
59
+ # Check if this repo has pyproject.toml or uv.lock
60
+ pyproject_path = repo_path / "pyproject.toml"
61
+ uv_lock_path = repo_path / "uv.lock"
62
+
63
+ # Get hashes before pull
64
+ pyproject_hash_before = get_file_hash(pyproject_path)
65
+ uv_lock_hash_before = get_file_hash(uv_lock_path)
66
+
67
+ # Get current commit hash before pull
68
+ commit_before = repo.head.commit.hexsha
69
+
70
+ try:
71
+ # Use subprocess for git pull to get better output control
72
+ dependencies_changed = False
73
+
74
+ # Get list of remotes
75
+ remotes = list(repo.remotes)
76
+ if not remotes:
77
+ print("⚠️ No remotes configured for this repository")
78
+ return False
79
+
80
+ for remote in remotes:
81
+ try:
82
+ print(f"📥 Fetching from {remote.name}...")
83
+
84
+ # Set up environment for git commands
85
+ env = None
86
+ if not allow_password_prompt:
87
+ # Disable interactive prompts
88
+ import os
89
+
90
+ env = os.environ.copy()
91
+ env["GIT_TERMINAL_PROMPT"] = "0"
92
+ env["GIT_ASKPASS"] = "echo" # Returns empty string for any credential request
93
+
94
+ # First fetch to see what's available
95
+ fetch_result = subprocess.run(
96
+ ["git", "fetch", remote.name, "--verbose"],
97
+ cwd=repo_path,
98
+ capture_output=True,
99
+ text=True,
100
+ env=env,
101
+ timeout=30, # Add timeout to prevent hanging
102
+ )
103
+
104
+ # Check if fetch failed due to authentication
105
+ if fetch_result.returncode != 0 and not allow_password_prompt:
106
+ auth_error_indicators = [
107
+ "Authentication failed",
108
+ "Password for",
109
+ "Username for",
110
+ "could not read Username",
111
+ "could not read Password",
112
+ "fatal: Authentication failed",
113
+ "fatal: could not read Username",
114
+ "fatal: could not read Password",
115
+ ]
116
+
117
+ error_output = (fetch_result.stderr or "") + (fetch_result.stdout or "")
118
+ if any(indicator in error_output for indicator in auth_error_indicators):
119
+ print(f"⚠️ Skipping {remote.name} - authentication required but password prompts are disabled")
120
+ continue
121
+
122
+ if fetch_result.stdout:
123
+ print(f"📡 Fetch output: {fetch_result.stdout.strip()}")
124
+ if fetch_result.stderr:
125
+ print(f"📡 Fetch info: {fetch_result.stderr.strip()}")
126
+
127
+ # Now pull with verbose output
128
+ print(f"📥 Pulling from {remote.name}/{repo.active_branch.name}...")
129
+ pull_result = subprocess.run(["git", "pull", remote.name, repo.active_branch.name, "--verbose"], cwd=repo_path, capture_output=True, text=True, env=env, timeout=30)
130
+
131
+ # Check if pull failed due to authentication
132
+ if pull_result.returncode != 0 and not allow_password_prompt:
133
+ auth_error_indicators = [
134
+ "Authentication failed",
135
+ "Password for",
136
+ "Username for",
137
+ "could not read Username",
138
+ "could not read Password",
139
+ "fatal: Authentication failed",
140
+ "fatal: could not read Username",
141
+ "fatal: could not read Password",
142
+ ]
143
+
144
+ error_output = (pull_result.stderr or "") + (pull_result.stdout or "")
145
+ if any(indicator in error_output for indicator in auth_error_indicators):
146
+ print(f"⚠️ Skipping pull from {remote.name} - authentication required but password prompts are disabled")
147
+ continue
148
+
149
+ if pull_result.stdout:
150
+ print(f"📦 Pull output: {pull_result.stdout.strip()}")
151
+ if pull_result.stderr:
152
+ print(f"📦 Pull info: {pull_result.stderr.strip()}")
153
+
154
+ # Check if pull was successful
155
+ if pull_result.returncode == 0:
156
+ # Check if commits changed
157
+ commit_after = repo.head.commit.hexsha
158
+ if commit_before != commit_after:
159
+ print(f"✅ Repository updated: {commit_before[:8]} → {commit_after[:8]}")
160
+ else:
161
+ print("✅ Already up to date")
162
+ else:
163
+ print(f"❌ Pull failed with return code {pull_result.returncode}")
164
+
165
+ except Exception as e:
166
+ print(f"⚠️ Failed to pull from {remote.name}: {e}")
167
+ continue
168
+
169
+ # Check if pyproject.toml or uv.lock changed after pull
170
+ pyproject_hash_after = get_file_hash(pyproject_path)
171
+ uv_lock_hash_after = get_file_hash(uv_lock_path)
172
+
173
+ if pyproject_hash_before != pyproject_hash_after:
174
+ print("📋 pyproject.toml has changed")
175
+ dependencies_changed = True
176
+
177
+ if uv_lock_hash_before != uv_lock_hash_after:
178
+ print("🔒 uv.lock has changed")
179
+ dependencies_changed = True
180
+
181
+ # Special handling for machineconfig repository
182
+ if "machineconfig" in str(repo_path):
183
+ print("🛠 Special handling for machineconfig repository...")
184
+ scripts_path = Path.home() / "scripts"
185
+ if scripts_path.exists():
186
+ set_permissions_recursive(scripts_path)
187
+ print(f"✅ Set permissions for {scripts_path}")
188
+
189
+ linux_jobs_path = repo_path / "src" / "machineconfig" / "jobs" / "linux"
190
+ if linux_jobs_path.exists():
191
+ set_permissions_recursive(linux_jobs_path)
192
+ print(f"✅ Set permissions for {linux_jobs_path}")
193
+
194
+ lf_exe_path = repo_path / "src" / "machineconfig" / "settings" / "lf" / "linux" / "exe"
195
+ if lf_exe_path.exists():
196
+ set_permissions_recursive(lf_exe_path)
197
+ print(f"✅ Set permissions for {lf_exe_path}")
198
+
199
+ # Run uv sync if dependencies changed and auto_sync is enabled
200
+ if dependencies_changed and auto_sync:
201
+ run_uv_sync(repo_path)
202
+
203
+ return dependencies_changed
204
+
205
+ except Exception as e:
206
+ print(f"❌ Error updating repository {repo_path}: {e}")
207
+ return False
208
+
209
+
210
+ def main(verbose: bool = True, allow_password_prompt: bool = False) -> str:
211
+ """Main function to update all configured repositories."""
14
212
  _ = verbose
15
- repos: list[str] = ["~/code/crocodile", "~/code/machineconfig", ]
213
+ repos: list[str] = ["~/code/machineconfig", "~/code/crocodile"]
16
214
  try:
17
- tmp = read_ini(DEFAULTS_PATH)['general']['repos'].split(",")
18
- if tmp[-1] == "": tmp = tmp[:-1]
215
+ tmp = read_ini(DEFAULTS_PATH)["general"]["repos"].split(",")
216
+ if tmp[-1] == "":
217
+ tmp = tmp[:-1]
19
218
  repos += tmp
20
219
  except (FileNotFoundError, KeyError, IndexError):
21
220
  print(f"""
@@ -34,57 +233,31 @@ def main(verbose: bool=True) -> str:
34
233
  │ to_email = myemail@email.com
35
234
  └────────────────────────────────────────────────────────────────""")
36
235
 
37
- repos_objs = []
236
+ # Process repositories
237
+ repos_with_changes = []
38
238
  for a_package_path in repos:
39
239
  try:
40
- import git
41
- repo = git.Repo(str(PathExtended(a_package_path).expanduser()), search_parent_directories=True)
42
- repos_objs.append(repo)
240
+ expanded_path = PathExtended(a_package_path).expanduser()
241
+ repo = git.Repo(str(expanded_path), search_parent_directories=True)
242
+
243
+ # Update repository and check if dependencies changed
244
+ dependencies_changed = update_repository(repo, allow_password_prompt=allow_password_prompt)
245
+
246
+ if dependencies_changed:
247
+ repos_with_changes.append(Path(repo.working_dir))
248
+
43
249
  except Exception as ex:
44
- print(f"""
45
- Repository Error:
46
- Path: {a_package_path}
47
- Exception: {ex}
48
- {'-' * 50}""")
49
-
50
- if system() == "Linux":
51
- additions = []
52
- for a_repo in repos_objs:
53
- if "machineconfig" in str(a_repo.working_dir): # special treatment because of executables.
54
- an_addition = f"""
55
- echo ""
56
- echo "🔄 {("Updating " + str(a_repo.working_dir)).center(80, "═")}"
57
- echo "🛠 Special handling for machineconfig repository..."
58
- cd "{a_repo.working_dir}"
59
- # git reset --hard
60
- git pull origin &
61
- chmod +x ~/scripts -R
62
- chmod +x ~/code/machineconfig/src/machineconfig/jobs/linux -R
63
- chmod +x ~/code/machineconfig/src/machineconfig/settings/lf/linux/exe -R
64
- """
65
- additions.append(an_addition)
66
- else:
67
- additions.append(f"""
68
- echo "🔄 {("Updating " + str(a_repo.working_dir)).center(80, "═")}"
69
- cd "{a_repo.working_dir}"
70
- {sep.join([f'git pull {remote.name} {a_repo.active_branch.name} &' for remote in a_repo.remotes])}
71
- """
72
- )
73
- program = "\n".join(additions)
74
-
75
- elif system() == "Windows":
76
- program = "\n".join([f"""
77
- echo "🔄 {("Updating " + str(a_repo.working_dir)).center(80, "═")}"
78
- cd "{a_repo.working_dir}"
79
- {sep.join([f'git pull {remote.name} {a_repo.active_branch.name}' for remote in a_repo.remotes])}
80
- """ for a_repo in repos_objs])
81
- else: raise NotImplementedError(f"""
82
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
83
- ┃ ⚠️ Unsupported System: {system()}
84
- ┃ ℹ️ This functionality is only available on Windows and Linux
85
- ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
86
- return program
250
+ print(f"""❌ Repository Error: Path: {a_package_path}
251
+ Exception: {ex}
252
+ {"-" * 50}""")
253
+
254
+ # Run uv sync for repositories where pyproject.toml or uv.lock changed
255
+ for repo_path in repos_with_changes:
256
+ run_uv_sync(repo_path)
257
+
258
+ # print("\n🎉 All repositories updated successfully!")
259
+ return """echo "🎉 All repositories updated successfully!" """
87
260
 
88
261
 
89
- if __name__ == '__main__':
90
- pass
262
+ if __name__ == "__main__":
263
+ main()
@@ -1,15 +1,13 @@
1
- """Like yadm and dotter.
2
- """
1
+ """Like yadm and dotter."""
3
2
 
4
-
5
- from machineconfig.utils.path_reduced import P as PathExtended
6
- from machineconfig.profile.create import symlink_func
7
- from machineconfig.utils.utils import LIBRARY_ROOT, REPO_ROOT
3
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
4
+ from machineconfig.utils.links import symlink_func
5
+ from machineconfig.utils.source_of_truth import LIBRARY_ROOT, REPO_ROOT
8
6
  import argparse
9
7
 
10
8
 
11
9
  def main():
12
- parser = argparse.ArgumentParser(description='FTP client')
10
+ parser = argparse.ArgumentParser(description="FTP client")
13
11
 
14
12
  parser.add_argument("file", help="file/folder path.", default="")
15
13
  # FLAGS
@@ -20,11 +18,15 @@ def main():
20
18
  args = parser.parse_args()
21
19
  orig_path = PathExtended(args.file).expanduser().absolute()
22
20
  if args.dest == "":
23
- if "Local" in str(orig_path): junction = orig_path.split(at="Local", sep=-1)[1]
24
- elif "Roaming" in str(orig_path): junction = orig_path.split(at="Roaming", sep=-1)[1]
25
- elif ".config" in str(orig_path): junction = orig_path.split(at=".config", sep=-1)[1]
26
- else: junction = orig_path.rel2home()
27
- new_path = REPO_ROOT.joinpath(junction)
21
+ if "Local" in str(orig_path):
22
+ junction = orig_path.split(at="Local", sep=-1)[1]
23
+ elif "Roaming" in str(orig_path):
24
+ junction = orig_path.split(at="Roaming", sep=-1)[1]
25
+ elif ".config" in str(orig_path):
26
+ junction = orig_path.split(at=".config", sep=-1)[1]
27
+ else:
28
+ junction = orig_path.rel2home()
29
+ new_path = PathExtended(REPO_ROOT).joinpath(junction)
28
30
  else:
29
31
  dest_path = PathExtended(args.dest).expanduser().absolute()
30
32
  dest_path.mkdir(parents=True, exist_ok=True)
@@ -39,12 +41,12 @@ def main():
39
41
  ┃ 🔄 To enshrine this mapping, add the following to mapper.toml:
40
42
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━""")
41
43
  print(f"""
42
- 📝 Edit configuration file: nano {LIBRARY_ROOT}/symlinks/mapper.toml
44
+ 📝 Edit configuration file: nano {PathExtended(LIBRARY_ROOT)}/symlinks/mapper.toml
43
45
 
44
46
  [{new_path.parent.name}]
45
- {orig_path.name.split('.')[0]} = {{ this = '{orig_path.collapseuser().as_posix()}', to_this = '{new_path.collapseuser().as_posix()}' }}
47
+ {orig_path.name.split(".")[0]} = {{ this = '{orig_path.collapseuser().as_posix()}', to_this = '{new_path.collapseuser().as_posix()}' }}
46
48
  """)
47
49
 
48
50
 
49
- if __name__ == '__main__':
51
+ if __name__ == "__main__":
50
52
  main()
@@ -1,4 +1,3 @@
1
-
2
1
  """Utility to launch multiple AI agent prompts in a Zellij session.
3
2
 
4
3
  Improved design notes:
@@ -17,14 +16,20 @@ from typing import Literal, TypeAlias, get_args, Iterable
17
16
 
18
17
  from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
19
18
  from machineconfig.utils.utils2 import randstr
19
+ import random
20
+ # import time
20
21
 
21
- AGENTS: TypeAlias = Literal["cursor-agent", "gemini", "crush"]
22
+ AGENTS: TypeAlias = Literal[
23
+ "cursor-agent", "gemini", "crush", "q", "onlyPrepPromptFiles"
24
+ # warp terminal
25
+ ]
22
26
  TabConfig = dict[str, tuple[str, str]] # tab name -> (cwd, command)
23
27
  DEFAULT_AGENT_CAP = 6
24
28
 
25
29
 
26
30
  def get_gemini_api_keys() -> list[str]:
27
31
  from machineconfig.utils.utils2 import read_ini
32
+
28
33
  config = read_ini(Path.home().joinpath("dotfiles/creds/llm/gemini/api_keys.ini"))
29
34
  res: list[str] = []
30
35
  for a_section_name in list(config.sections()):
@@ -41,12 +46,17 @@ def get_gemini_api_keys() -> list[str]:
41
46
  def _search_python_files(repo_root: Path, keyword: str) -> list[Path]:
42
47
  """Return all Python files under repo_root whose text contains keyword.
43
48
 
44
- Errors reading individual files are ignored (decoded with 'ignore').
49
+ Notes:
50
+ - Skips any paths that reside under a directory named ".venv" at any depth.
51
+ - Errors reading individual files are ignored (decoded with 'ignore').
45
52
  """
46
53
  py_files = list(repo_root.rglob("*.py"))
47
54
  keyword_lower = keyword.lower()
48
55
  matches: list[Path] = []
49
56
  for f in py_files:
57
+ # Skip anything under a .venv directory anywhere in the path
58
+ if any(part == ".venv" for part in f.parts):
59
+ continue
50
60
  try:
51
61
  if keyword_lower in f.read_text(encoding="utf-8", errors="ignore").lower():
52
62
  matches.append(f)
@@ -59,6 +69,8 @@ def _search_python_files(repo_root: Path, keyword: str) -> list[Path]:
59
69
  def _write_list_file(target: Path, files: Iterable[Path]) -> None:
60
70
  target.parent.mkdir(parents=True, exist_ok=True)
61
71
  target.write_text("\n".join(str(f) for f in files), encoding="utf-8")
72
+
73
+
62
74
  def _chunk_prompts(prompts: list[str], max_agents: int) -> list[str]:
63
75
  prompts = [p for p in prompts if p.strip() != ""] # drop blank entries
64
76
  if len(prompts) <= max_agents:
@@ -69,6 +81,8 @@ def _chunk_prompts(prompts: list[str], max_agents: int) -> list[str]:
69
81
  for i in range(0, len(prompts), chunk_size):
70
82
  grouped.append("\nTargeted Locations:\n".join(prompts[i : i + chunk_size]))
71
83
  return grouped
84
+
85
+
72
86
  def _confirm(message: str, default_no: bool = True) -> bool:
73
87
  suffix = "[y/N]" if default_no else "[Y/n]"
74
88
  answer = input(f"{message} {suffix} ").strip().lower()
@@ -89,11 +103,7 @@ def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_age
89
103
  raise ValueError("No prompts provided")
90
104
 
91
105
  if len(prompts) > max_agents:
92
- proceed = _confirm(
93
- message=(
94
- f"You are about to launch {len(prompts)} agents which exceeds the cap ({max_agents}). Proceed?"
95
- )
96
- )
106
+ proceed = _confirm(message=(f"You are about to launch {len(prompts)} agents which exceeds the cap ({max_agents}). Proceed?"))
97
107
  if not proceed:
98
108
  print("Aborting per user choice.")
99
109
  return {}
@@ -105,6 +115,7 @@ def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_age
105
115
  for idx, a_prompt in enumerate(prompts):
106
116
  prompt_path = tmp_dir / f"agent{idx}_prompt.txt"
107
117
  prompt_path.write_text(a_prompt, encoding="utf-8")
118
+ cmd_path = tmp_dir / f"agent{idx}_cmd.sh"
108
119
  match agent:
109
120
  case "gemini":
110
121
  # model = "gemini-2.5-pro"
@@ -122,27 +133,45 @@ def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_age
122
133
  cmd = f"""
123
134
  export GEMINI_API_KEY={shlex.quote(api_key)}
124
135
  echo "Using Gemini API key $GEMINI_API_KEY"
125
- echo "Launching gemini agent with prompt from {shlex.quote(str(prompt_path))}"
126
136
  cat {prompt_path}
127
137
  GEMINI_API_KEY={shlex.quote(api_key)} bash -lc 'cat {safe_path} | gemini {model_arg} --yolo --prompt'
128
138
  """
129
139
  case "cursor-agent":
130
140
  # As originally implemented
131
141
  cmd = f"""
132
- echo "Launching cursor-agent with prompt from {shlex.quote(str(prompt_path))}"
133
- cat {prompt_path}
142
+
134
143
  cursor-agent --print --output-format text < {prompt_path}
144
+
135
145
  """
136
146
  case "crush":
137
147
  cmd = f"""
138
- echo "Launching crush with prompt from {shlex.quote(str(prompt_path))}"
139
- cat {prompt_path}
140
- cat {prompt_path} | crush run
148
+ # cat {prompt_path} | crush run
149
+ crush run {prompt_path}
150
+ """
151
+ case "q":
152
+ cmd = f"""
153
+ q chat --no-interactive --trust-all-tools {prompt_path}
154
+ """
155
+ case "onlyPrepPromptFiles":
156
+ cmd = f"""
157
+ echo "Prepared prompt file at {prompt_path}"
141
158
  """
142
159
  case _:
143
160
  raise ValueError(f"Unsupported agent type: {agent}")
144
- cmd_path = tmp_dir / f"agent{idx}_cmd.sh"
145
- cmd_path.write_text(cmd, encoding="utf-8")
161
+ random_sleep_time = random.uniform(0, 5)
162
+ cmd_prefix = f"""
163
+ echo "Sleeping for {random_sleep_time:.2f} seconds to stagger agent startups..."
164
+ sleep {random_sleep_time:.2f}
165
+ echo "Launching `{agent}` with prompt from {prompt_path}"
166
+ echo "Launching `{agent}` with command from {cmd_path}"
167
+ echo "--------START OF AGENT OUTPUT--------"
168
+ sleep 0.1
169
+ """
170
+ cmd_postfix = """
171
+ sleep 0.1
172
+ echo "---------END OF AGENT OUTPUT---------"
173
+ """
174
+ cmd_path.write_text(cmd_prefix + cmd + cmd_postfix, encoding="utf-8")
146
175
  fire_cmd = f"bash {shlex.quote(str(cmd_path))}"
147
176
  tab_config[f"Agent{idx}"] = (str(repo_root), fire_cmd)
148
177
 
@@ -189,18 +218,15 @@ def main(): # noqa: C901 - (complexity acceptable for CLI glue)
189
218
  combined_prompts = [prefix + "\n" + p for p in combined_prompts]
190
219
 
191
220
  from machineconfig.utils.options import choose_one_option
221
+
192
222
  agent_selected = choose_one_option(header="Select agent type", options=get_args(AGENTS))
193
223
 
194
- tab_config = launch_agents(
195
- repo_root=repo_root,
196
- prompts=combined_prompts,
197
- agent=agent_selected,
198
- max_agents=DEFAULT_AGENT_CAP,
199
- )
224
+ tab_config = launch_agents(repo_root=repo_root, prompts=combined_prompts, agent=agent_selected, max_agents=DEFAULT_AGENT_CAP)
200
225
  if not tab_config:
201
226
  return
202
227
 
203
228
  from machineconfig.utils.utils2 import randstr
229
+
204
230
  random_name = randstr(length=3)
205
231
  manager = ZellijLocalManager(session2zellij_tabs={"Agents": tab_config}, session_name_prefix=random_name)
206
232
  manager.start_all_sessions()