machineconfig 5.37__py3-none-any.whl → 5.39__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 (117) hide show
  1. machineconfig/cluster/remote/script_execution.py +0 -1
  2. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +2 -4
  3. machineconfig/jobs/installer/check_installations.py +2 -2
  4. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +12 -12
  5. machineconfig/jobs/installer/installer_data.json +53 -2
  6. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -3
  7. machineconfig/profile/create_helper.py +26 -0
  8. machineconfig/profile/{create.py → create_links.py} +6 -6
  9. machineconfig/profile/{create_frontend.py → create_links_export.py} +6 -10
  10. machineconfig/profile/{shell.py → create_shell_profile.py} +12 -37
  11. machineconfig/scripts/linux/{share_cloud.sh → other/share_cloud.sh} +3 -0
  12. machineconfig/scripts/linux/z_ls +2 -2
  13. machineconfig/scripts/python/agents.py +0 -1
  14. machineconfig/scripts/python/ai/initai.py +3 -4
  15. machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +0 -1
  16. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +0 -1
  17. machineconfig/scripts/python/ai/solutions/generic.py +1 -0
  18. machineconfig/scripts/python/croshell.py +12 -2
  19. machineconfig/scripts/python/croshell_helpers/start_slidev.py +2 -2
  20. machineconfig/scripts/python/devops.py +4 -21
  21. machineconfig/scripts/python/devops_helpers/cli_config.py +8 -8
  22. machineconfig/scripts/python/devops_helpers/cli_config_dotfile.py +3 -3
  23. machineconfig/scripts/python/devops_helpers/cli_nw.py +2 -1
  24. machineconfig/scripts/python/devops_helpers/cli_repos.py +1 -3
  25. machineconfig/scripts/python/devops_helpers/cli_self.py +22 -9
  26. machineconfig/scripts/python/devops_helpers/cli_share_server.py +109 -0
  27. machineconfig/scripts/python/devops_helpers/cli_terminal.py +35 -23
  28. machineconfig/scripts/python/devops_helpers/devops_status.py +7 -80
  29. machineconfig/scripts/python/devops_helpers/devops_update_repos.py +64 -45
  30. machineconfig/scripts/python/devops_helpers/themes/choose_pwsh_theme.ps1 +16 -15
  31. machineconfig/scripts/python/devops_navigator.py +183 -80
  32. machineconfig/scripts/python/fire_jobs.py +4 -1
  33. machineconfig/scripts/python/ftpx.py +0 -1
  34. machineconfig/scripts/python/helpers_fire/{fire_gemini.py → agentic_frameworks/fire_gemini.py} +12 -9
  35. machineconfig/scripts/python/helpers_fire/agentic_frameworks/fire_qwen.py +43 -0
  36. machineconfig/scripts/python/helpers_fire/fire_agents_help_launch.py +4 -4
  37. machineconfig/scripts/python/helpers_fire/template.ps1 +29 -0
  38. machineconfig/scripts/python/helpers_fire/template.sh +1 -1
  39. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +3 -3
  40. machineconfig/scripts/python/interactive.py +5 -4
  41. machineconfig/scripts/python/nw/mount_nfs +1 -1
  42. machineconfig/scripts/python/nw/mount_nw_drive +1 -2
  43. machineconfig/scripts/python/repos_helpers/count_lines_frontend.py +1 -1
  44. machineconfig/scripts/python/repos_helpers/entrypoint.py +2 -2
  45. machineconfig/scripts/python/repos_helpers/record.py +2 -2
  46. machineconfig/scripts/python/sessions_helpers/sessions_multiprocess.py +3 -1
  47. machineconfig/scripts/windows/{mount_nfs.ps1 → mounts/mount_nfs.ps1} +1 -3
  48. machineconfig/scripts/windows/{mount_ssh.ps1 → mounts/mount_ssh.ps1} +1 -1
  49. machineconfig/settings/lf/linux/lfrc +4 -7
  50. machineconfig/settings/lf/windows/lfrc +4 -22
  51. machineconfig/settings/lvim/windows/archive/config_additional.lua +0 -6
  52. machineconfig/settings/pistol/pistol.conf +1 -1
  53. machineconfig/settings/shells/bash/init.sh +9 -8
  54. machineconfig/settings/shells/pwsh/init.ps1 +10 -4
  55. machineconfig/settings/svim/linux/init.toml +0 -4
  56. machineconfig/settings/svim/windows/init.toml +0 -3
  57. machineconfig/setup_linux/web_shortcuts/interactive.sh +22 -0
  58. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +34 -1
  59. machineconfig/utils/files/dbms.py +4 -1
  60. machineconfig/utils/installer_utils/installer.py +12 -0
  61. machineconfig/utils/installer_utils/installer_abc.py +26 -9
  62. machineconfig/utils/installer_utils/installer_class.py +1 -1
  63. machineconfig/utils/io.py +0 -18
  64. machineconfig/utils/scheduler.py +3 -4
  65. machineconfig/utils/source_of_truth.py +2 -4
  66. machineconfig/utils/ssh.py +1 -1
  67. {machineconfig-5.37.dist-info → machineconfig-5.39.dist-info}/METADATA +8 -4
  68. {machineconfig-5.37.dist-info → machineconfig-5.39.dist-info}/RECORD +84 -114
  69. machineconfig/jobs/windows/start_terminal.ps1 +0 -6
  70. machineconfig/jobs/windows/startup_file.cmd +0 -2
  71. machineconfig/scripts/cloud/init.sh +0 -105
  72. machineconfig/scripts/linux/agents +0 -2
  73. machineconfig/scripts/linux/cloud +0 -2
  74. machineconfig/scripts/linux/croshell +0 -3
  75. machineconfig/scripts/linux/devops +0 -2
  76. machineconfig/scripts/linux/fire +0 -2
  77. machineconfig/scripts/linux/ftpx +0 -2
  78. machineconfig/scripts/linux/kill_process +0 -2
  79. machineconfig/scripts/linux/sessions +0 -2
  80. machineconfig/scripts/linux/start_terminals +0 -3
  81. machineconfig/scripts/windows/agents.ps1 +0 -1
  82. machineconfig/scripts/windows/cloud.ps1 +0 -1
  83. machineconfig/scripts/windows/croshell.ps1 +0 -1
  84. machineconfig/scripts/windows/devops.ps1 +0 -1
  85. machineconfig/scripts/windows/fire.ps1 +0 -1
  86. machineconfig/scripts/windows/ftpx.ps1 +0 -1
  87. machineconfig/scripts/windows/gpt.ps1 +0 -1
  88. machineconfig/scripts/windows/grep.ps1 +0 -2
  89. machineconfig/scripts/windows/kill_process.ps1 +0 -1
  90. machineconfig/scripts/windows/nano.ps1 +0 -3
  91. machineconfig/scripts/windows/pomodoro.ps1 +0 -1
  92. machineconfig/scripts/windows/reload_path.ps1 +0 -3
  93. machineconfig/scripts/windows/scheduler.ps1 +0 -1
  94. machineconfig/scripts/windows/sessions.ps1 +0 -1
  95. machineconfig/scripts/windows/snapshot.ps1 +0 -1
  96. machineconfig/scripts/windows/start_slidev.ps1 +0 -1
  97. machineconfig/scripts/windows/start_terminals.ps1 +0 -1
  98. machineconfig/scripts/windows/wsl_rdp_windows_port_forwarding.ps1 +0 -46
  99. machineconfig/scripts/windows/wsl_ssh_windows_port_forwarding.ps1 +0 -76
  100. machineconfig/setup_linux/machineconfig.sh +0 -20
  101. machineconfig/setup_windows/machineconfig.ps1 +0 -27
  102. /machineconfig/scripts/linux/{share_nfs → other/share_nfs} +0 -0
  103. /machineconfig/scripts/linux/{share_smb → other/share_smb} +0 -0
  104. /machineconfig/scripts/linux/{start_docker → other/start_docker} +0 -0
  105. /machineconfig/scripts/linux/{switch_ip → other/switch_ip} +0 -0
  106. /machineconfig/scripts/{windows/share_nfs.ps1 → python/helpers_fire/agentic_frameworks/__init__.py} +0 -0
  107. /machineconfig/scripts/python/helpers_fire/{fire_crush.json → agentic_frameworks/fire_crush.json} +0 -0
  108. /machineconfig/scripts/python/helpers_fire/{fire_crush.py → agentic_frameworks/fire_crush.py} +0 -0
  109. /machineconfig/scripts/python/helpers_fire/{fire_cursor_agents.py → agentic_frameworks/fire_cursor_agents.py} +0 -0
  110. /machineconfig/scripts/windows/{mount_nw.ps1 → mounts/mount_nw.ps1} +0 -0
  111. /machineconfig/scripts/windows/{mount_smb.ps1 → mounts/mount_smb.ps1} +0 -0
  112. /machineconfig/scripts/windows/{share_cloud.cmd → mounts/share_cloud.cmd} +0 -0
  113. /machineconfig/scripts/windows/{share_smb.ps1 → mounts/share_smb.ps1} +0 -0
  114. /machineconfig/scripts/windows/{unlock_bitlocker.ps1 → mounts/unlock_bitlocker.ps1} +0 -0
  115. {machineconfig-5.37.dist-info → machineconfig-5.39.dist-info}/WHEEL +0 -0
  116. {machineconfig-5.37.dist-info → machineconfig-5.39.dist-info}/entry_points.txt +0 -0
  117. {machineconfig-5.37.dist-info → machineconfig-5.39.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
 
2
2
  import machineconfig.scripts.python.devops_helpers.cli_terminal as cli_terminal
3
+ import machineconfig.scripts.python.devops_helpers.cli_share_server as cli_share_server
3
4
  import typer
4
5
  from typing import Optional
5
6
 
@@ -7,7 +8,7 @@ nw_apps = typer.Typer(help="🔐 Network subcommands", no_args_is_help=True)
7
8
 
8
9
 
9
10
  nw_apps.command(name="share-terminal", help="📡 Share terminal via web browser")(cli_terminal.main)
10
-
11
+ nw_apps.command(name="share-server", help="🌐 Start local/global server to share files/folders via web browser", no_args_is_help=True)(cli_share_server.main)
11
12
 
12
13
  @nw_apps.command()
13
14
  def install_ssh_server():
@@ -8,7 +8,6 @@ in the event that username@github.com is not mentioned in the remote url.
8
8
  from pathlib import Path
9
9
  from typing import Annotated, Optional
10
10
  import typer
11
- from git import Repo, InvalidGitRepositoryError
12
11
  from machineconfig.scripts.python.helpers_repos.secure_repo import main as secure_repo_main
13
12
 
14
13
 
@@ -26,7 +25,6 @@ CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁
26
25
  def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
27
26
  """🚀 Push changes across repositories."""
28
27
  from machineconfig.scripts.python.repos_helpers.entrypoint import git_operations
29
-
30
28
  git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, no_sync=no_sync)
31
29
 
32
30
 
@@ -143,7 +141,7 @@ def cleanup(repo: DirectoryArgument = None, recursive: RecursiveOption = False)
143
141
  repo = Path.cwd().as_posix()
144
142
 
145
143
  arg_path = Path(repo).expanduser().absolute()
146
-
144
+ from git import Repo, InvalidGitRepositoryError
147
145
  if not recursive:
148
146
  # Check if the directory is a git repo
149
147
  try:
@@ -1,41 +1,54 @@
1
1
 
2
+ from git import Optional
2
3
  import typer
3
4
 
4
- self_app = typer.Typer(help="🔄 SELF operations subcommands", no_args_is_help=True)
5
+ cli_app = typer.Typer(help="🔄 SELF operations subcommands", no_args_is_help=True)
5
6
 
6
7
 
7
- @self_app.command()
8
+ @cli_app.command()
8
9
  def update():
9
10
  """🔄 UPDATE essential repos"""
10
11
  import machineconfig.scripts.python.devops_helpers.devops_update_repos as helper
11
12
  helper.main()
12
- @self_app.command()
13
+ @cli_app.command()
13
14
  def interactive():
14
15
  """🤖 INTERACTIVE configuration of machine."""
15
16
  from machineconfig.scripts.python.interactive import main
16
17
  main()
17
- @self_app.command()
18
+ @cli_app.command()
18
19
  def status():
19
20
  """📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
20
21
  import machineconfig.scripts.python.devops_helpers.devops_status as helper
21
22
  helper.main()
22
- @self_app.command()
23
+ @cli_app.command()
23
24
  def clone():
24
25
  """📋 CLONE machienconfig locally and incorporate to shell profile for faster execution and nightly updates."""
25
26
  import platform
26
27
  from machineconfig.utils.code import run_shell_script
27
- from machineconfig.profile.shell import create_default_shell_profile
28
+ from machineconfig.profile.create_shell_profile import create_default_shell_profile
28
29
  if platform.system() == "Windows":
29
30
  from machineconfig.setup_windows import MACHINECONFIG
30
- create_default_shell_profile(method="copy")
31
+ create_default_shell_profile()
31
32
  else:
32
33
  from machineconfig.setup_linux import MACHINECONFIG
33
- create_default_shell_profile(method="reference")
34
+ create_default_shell_profile()
34
35
  run_shell_script(MACHINECONFIG.read_text(encoding="utf-8"))
35
36
 
36
- @self_app.command()
37
+ @cli_app.command(no_args_is_help=False)
37
38
  def navigate():
38
39
  """📚 NAVIGATE command structure with TUI"""
39
40
  from machineconfig.scripts.python.devops_navigator import main
40
41
  main()
41
42
 
43
+
44
+ @cli_app.command(no_args_is_help=True)
45
+ def run_python(ip: str = typer.Argument(..., help="Python command to run in the machineconfig environment"),
46
+ command: Optional[bool] = typer.Option(False, "--command", "-c", help="Run as command")):
47
+ """🐍 RUN python command/file in the machineconfig environment"""
48
+ if command:
49
+ exec(ip)
50
+ return
51
+ import machineconfig
52
+ import subprocess
53
+ import sys
54
+ subprocess.run([sys.executable, ip], cwd=machineconfig.__path__[0])
@@ -0,0 +1,109 @@
1
+ from pathlib import Path
2
+ from typing import Optional, Annotated
3
+ import typer
4
+ # import typer
5
+
6
+
7
+
8
+ def display_share_url(local_ip_v4: str, port: int, protocol: str = "http") -> None:
9
+ """Display a flashy, unmissable share URL announcement."""
10
+
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.text import Text
14
+ from rich.align import Align
15
+ console = Console()
16
+
17
+ # Create the main message with styling
18
+ url_text = Text(f"{protocol}://{local_ip_v4}:{port}", style="bold bright_cyan underline")
19
+ message = Text.assemble(
20
+ ("🚀 ", "bright_red"),
21
+ ("Share server is now accessible at: ", "bright_white bold"),
22
+ url_text,
23
+ (" 🚀", "bright_red")
24
+ )
25
+
26
+ # Create a fancy panel with borders and styling
27
+ panel = Panel(
28
+ Align.center(message),
29
+ title="[bold bright_green]🌐 SHARE SERVER READY 🌐[/bold bright_green]",
30
+ subtitle="[italic bright_yellow]⚡ Click the link above to access your shared files! ⚡[/italic bright_yellow]",
31
+ border_style="bright_magenta",
32
+ padding=(1, 2),
33
+ expand=False
34
+ )
35
+
36
+ # Print with extra spacing and attention-grabbing elements
37
+ console.print(panel)
38
+
39
+
40
+ def main(
41
+ path: Annotated[str, typer.Argument(help="Path to the file or directory to share")],
42
+ port: Annotated[Optional[int], typer.Option("--port", "-p", help="Port to run the share server on (default: 8080)")] = None,
43
+ username: Annotated[Optional[str], typer.Option("--username", "-u", help="Username for share access (default: current user)")] = None,
44
+ password: Annotated[Optional[str], typer.Option("--password", "-w", help="Password for share access (default: from ~/dotfiles/creds/passwords/quick_password)")] = None,
45
+ over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the share server over the internet using ngrok")] = False
46
+ ) -> None:
47
+ from machineconfig.utils.installer_utils.installer import install_if_missing
48
+ install_if_missing("ezshare")
49
+ if over_internet: install_if_missing("ngrok")
50
+
51
+ if username is None:
52
+ import getpass
53
+ username = getpass.getuser()
54
+ if password is None:
55
+ pwd_path = Path.home().joinpath("dotfiles/creds/passwords/quick_password")
56
+ if pwd_path.exists():
57
+ password = pwd_path.read_text(encoding="utf-8").strip()
58
+ else:
59
+ raise ValueError("Password not provided and default password file does not exist.")
60
+
61
+ if port is None:
62
+ port = 8080 # Default port for ezshare
63
+
64
+ import socket
65
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
66
+ s.connect(('8.8.8.8',80))
67
+ local_ip_v4 = s.getsockname()[0]
68
+ s.close()
69
+
70
+ # Display the flashy share announcement
71
+ protocol = "http"
72
+ display_share_url(local_ip_v4, port, protocol)
73
+ import subprocess
74
+ import time
75
+ # Build ezshare command
76
+ ezshare_cmd = f"ezshare --port {port} --username {username} --password {password} {path}"
77
+ ezshare_process = subprocess.Popen(ezshare_cmd, shell=True)
78
+ processes = [ezshare_process]
79
+
80
+ if over_internet:
81
+ ngrok_process = subprocess.Popen(f"ngrok http {port}", shell=True)
82
+ processes.append(ngrok_process)
83
+ time.sleep(3)
84
+ try:
85
+ import requests
86
+ response = requests.get("http://localhost:4040/api/tunnels")
87
+ data = response.json()
88
+ public_url = data['tunnels'][0]['public_url']
89
+ print(f"🌐 Ngrok tunnel ready: {public_url}")
90
+ except Exception as e:
91
+ print(f"Could not retrieve ngrok URL: {e}")
92
+
93
+ try:
94
+ while True:
95
+ print("Share server is running. Press Ctrl+C to stop.")
96
+ time.sleep(2)
97
+ except KeyboardInterrupt:
98
+ print("\nTerminating processes...")
99
+ for p in processes:
100
+ p.terminate()
101
+ p.wait()
102
+
103
+
104
+ def main_with_parser():
105
+ typer.run(main)
106
+
107
+
108
+ if __name__ == "__main__":
109
+ pass
@@ -1,8 +1,8 @@
1
-
2
-
3
1
  from pathlib import Path
4
2
  from typing import Optional, Annotated
5
3
  import typer
4
+ import subprocess
5
+ import time
6
6
 
7
7
 
8
8
 
@@ -49,19 +49,6 @@ def display_terminal_url(local_ip_v4: str, port: int, protocol: str = "http") ->
49
49
  # console.print("🔥" * 60 + "\n", style="bright_red bold")
50
50
 
51
51
 
52
- def install_ttyd():
53
- # uv run --python 3.13 --with machineconfig devops install ttyd
54
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
55
- exists = check_tool_exists("ttyd")
56
- if exists:
57
- print("✅ ttyd is already installed.")
58
- return
59
- print("⏳ ttyd not found. Installing...")
60
- from machineconfig.utils.installer_utils.installer import main
61
- main(which="ttyd")
62
-
63
-
64
-
65
52
  def main(
66
53
  port: Annotated[Optional[int], typer.Option("--port", "-p", help="Port to run the terminal server on (default: 7681)")] = None,
67
54
  username: Annotated[Optional[str], typer.Option("--username", "-u", help="Username for terminal access (default: current user)")] = None,
@@ -70,9 +57,13 @@ def main(
70
57
  ssl: Annotated[bool, typer.Option("--ssl", "-S", help="Enable SSL")] = False,
71
58
  ssl_cert: Annotated[Optional[str], typer.Option("--ssl-cert", "-C", help="SSL certificate file path")] = None,
72
59
  ssl_key: Annotated[Optional[str], typer.Option("--ssl-key", "-K", help="SSL key file path")] = None,
73
- ssl_ca: Annotated[Optional[str], typer.Option("--ssl-ca", "-A", help="SSL CA file path for client certificate verification")] = None
60
+ ssl_ca: Annotated[Optional[str], typer.Option("--ssl-ca", "-A", help="SSL CA file path for client certificate verification")] = None,
61
+ over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the terminal over the internet using ngrok")] = False
74
62
  ) -> None:
75
- install_ttyd()
63
+ from machineconfig.utils.installer_utils.installer import install_if_missing
64
+ install_if_missing("ttyd")
65
+ if over_internet: install_if_missing("ngrok")
66
+
76
67
  if username is None:
77
68
  import getpass
78
69
  username = getpass.getuser()
@@ -128,12 +119,33 @@ def main(
128
119
  start_command = "powershell"
129
120
  else:
130
121
  start_command = "bash"
131
- code = f"""
132
- #!/bin/bash
133
- ttyd --writable -t enableSixel=true {ssl_args} --port {port} --credential "{username}:{password}" -t 'theme={{"background": "black"}}' {start_command}
134
- """
135
- from machineconfig.utils.code import run_shell_script
136
- run_shell_script(code, display_script=False)
122
+
123
+ ttyd_cmd = f"ttyd --writable -t enableSixel=true {ssl_args} --port {port} --credential \"{username}:{password}\" -t 'theme={{\"background\": \"black\"}}' {start_command}"
124
+ ttyd_process = subprocess.Popen(ttyd_cmd, shell=True)
125
+ processes = [ttyd_process]
126
+
127
+ if over_internet:
128
+ ngrok_process = subprocess.Popen(f"ngrok http {port}", shell=True)
129
+ processes.append(ngrok_process)
130
+ time.sleep(3)
131
+ try:
132
+ import requests
133
+ response = requests.get("http://localhost:4040/api/tunnels")
134
+ data = response.json()
135
+ public_url = data['tunnels'][0]['public_url']
136
+ print(f"🌐 Ngrok tunnel ready: {public_url}")
137
+ except Exception as e:
138
+ print(f"Could not retrieve ngrok URL: {e}")
139
+
140
+ try:
141
+ while True:
142
+ print("Terminal server is running. Press Ctrl+C to stop.")
143
+ time.sleep(2)
144
+ except KeyboardInterrupt:
145
+ print("\nTerminating processes...")
146
+ for p in processes:
147
+ p.terminate()
148
+ p.wait()
137
149
 
138
150
 
139
151
  def main_with_parser():
@@ -11,7 +11,7 @@ from rich.table import Table
11
11
  from rich.text import Text
12
12
 
13
13
  from machineconfig.utils.path_extended import PathExtended
14
- from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH, LIBRARY_ROOT
14
+ from machineconfig.utils.source_of_truth import CONFIG_ROOT, DEFAULTS_PATH, LIBRARY_ROOT
15
15
 
16
16
 
17
17
  console = Console()
@@ -36,7 +36,7 @@ def _check_system_info() -> dict[str, str]:
36
36
 
37
37
  def _check_shell_profile_status() -> dict[str, Any]:
38
38
  """Check shell profile configuration status."""
39
- from machineconfig.profile.shell import get_shell_profile_path
39
+ from machineconfig.profile.create_shell_profile import get_shell_profile_path
40
40
 
41
41
  try:
42
42
  profile_path = get_shell_profile_path()
@@ -46,13 +46,13 @@ def _check_shell_profile_status() -> dict[str, Any]:
46
46
  profile_content = profile_path.read_text(encoding="utf-8")
47
47
  system_name = platform.system()
48
48
  if system_name == "Windows":
49
- init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/pwsh/init.ps1")
50
- init_script_copy = PathExtended(CONFIG_PATH).joinpath("profile/init.ps1").collapseuser()
49
+ init_script = PathExtended(CONFIG_ROOT).joinpath("settings/shells/pwsh/init.ps1")
50
+ init_script_copy = PathExtended(CONFIG_ROOT).joinpath("profile/init.ps1").collapseuser()
51
51
  source_reference = f". {str(init_script.collapseuser()).replace('~', '$HOME')}"
52
52
  source_copy = f". {str(init_script_copy).replace('~', '$HOME')}"
53
53
  else:
54
- init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/bash/init.sh")
55
- init_script_copy = PathExtended(CONFIG_PATH).joinpath("profile/init.sh").collapseuser()
54
+ init_script = PathExtended(CONFIG_ROOT).joinpath("settings/shells/bash/init.sh")
55
+ init_script_copy = PathExtended(CONFIG_ROOT).joinpath("profile/init.sh").collapseuser()
56
56
  source_reference = f"source {str(init_script.collapseuser()).replace('~', '$HOME')}"
57
57
  source_copy = f"source {str(init_script_copy).replace('~', '$HOME')}"
58
58
 
@@ -79,34 +79,6 @@ def _check_shell_profile_status() -> dict[str, Any]:
79
79
  }
80
80
 
81
81
 
82
- def _check_machineconfig_repo() -> dict[str, Any]:
83
- """Check machineconfig repository status."""
84
- repo_path = Path.home().joinpath("code/machineconfig")
85
- if not repo_path.exists():
86
- return {"exists": False, "is_repo": False, "clean": False, "branch": "N/A", "commit": "N/A", "remotes": []}
87
-
88
- try:
89
- import git
90
-
91
- repo = git.Repo(str(repo_path))
92
- is_dirty = repo.is_dirty(untracked_files=True)
93
- current_branch = repo.active_branch.name if not repo.head.is_detached else "DETACHED"
94
- current_commit = repo.head.commit.hexsha[:8]
95
- remotes = [remote.name for remote in repo.remotes]
96
-
97
- return {
98
- "exists": True,
99
- "is_repo": True,
100
- "clean": not is_dirty,
101
- "branch": current_branch,
102
- "commit": current_commit,
103
- "remotes": remotes,
104
- "path": str(repo_path),
105
- }
106
- except Exception as ex:
107
- return {"exists": True, "is_repo": False, "clean": False, "branch": "Error", "commit": "N/A", "remotes": [], "error": str(ex)}
108
-
109
-
110
82
  def _check_repos_status() -> dict[str, Any]:
111
83
  """Check configured repositories status."""
112
84
  from machineconfig.utils.io import read_ini
@@ -178,7 +150,7 @@ def _check_ssh_status() -> dict[str, Any]:
178
150
 
179
151
  def _check_config_files_status() -> dict[str, Any]:
180
152
  """Check public and private configuration files status."""
181
- from machineconfig.profile.create import read_mapper
153
+ from machineconfig.profile.create_links import read_mapper
182
154
 
183
155
  try:
184
156
  mapper = read_mapper()
@@ -320,48 +292,6 @@ def _display_shell_status(status: dict[str, Any]) -> None:
320
292
  )
321
293
 
322
294
 
323
- def _display_machineconfig_repo(info: dict[str, Any]) -> None:
324
- """Display machineconfig repository status."""
325
- console.rule("[bold magenta]📦 Machineconfig Repository[/bold magenta]")
326
-
327
- if not info["exists"]:
328
- console.print(
329
- Panel(
330
- "❌ Machineconfig repository not found at ~/code/machineconfig",
331
- title="Repository Status",
332
- border_style="red",
333
- padding=(1, 2),
334
- expand=False,
335
- )
336
- )
337
- return
338
-
339
- if not info["is_repo"]:
340
- console.print(
341
- Panel(
342
- f"❌ Directory exists but is not a git repository\n{info.get('error', 'Unknown error')}",
343
- title="Repository Status",
344
- border_style="red",
345
- padding=(1, 2),
346
- expand=False,
347
- )
348
- )
349
- return
350
-
351
- table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
352
- table.add_column("Property", style="cyan", no_wrap=True)
353
- table.add_column("Value", style="white")
354
-
355
- table.add_row("📁 Path", info["path"])
356
- table.add_row("🌿 Branch", info["branch"])
357
- table.add_row("🔖 Commit", info["commit"])
358
- table.add_row(f"{'✅' if info['clean'] else '⚠️'} Status", "Clean" if info["clean"] else "Uncommitted changes")
359
- table.add_row("📡 Remotes", ", ".join(info["remotes"]) if info["remotes"] else "None")
360
-
361
- border_style = "green" if info["clean"] else "yellow"
362
- console.print(Panel(table, title="Machineconfig Repository", border_style=border_style, padding=(1, 2), expand=False))
363
-
364
-
365
295
  def _display_repos_status(status: dict[str, Any]) -> None:
366
296
  """Display configured repositories status."""
367
297
  console.rule("[bold cyan]📚 Configured Repositories[/bold cyan]")
@@ -557,9 +487,6 @@ def main() -> None:
557
487
  shell_status = _check_shell_profile_status()
558
488
  _display_shell_status(shell_status)
559
489
 
560
- machineconfig_repo = _check_machineconfig_repo()
561
- _display_machineconfig_repo(machineconfig_repo)
562
-
563
490
  repos_status = _check_repos_status()
564
491
  _display_repos_status(repos_status)
565
492
 
@@ -1,5 +1,6 @@
1
1
  """Update repositories with fancy output"""
2
2
 
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3
4
  from pathlib import Path
4
5
 
5
6
  import git
@@ -16,6 +17,55 @@ from machineconfig.utils.source_of_truth import DEFAULTS_PATH
16
17
  console = Console()
17
18
 
18
19
 
20
+ def _process_single_repo(expanded_path: Path, allow_password_prompt: bool) -> tuple[RepositoryUpdateResult, Path | None]:
21
+ """Process a single repository and return the result."""
22
+ try:
23
+ repo = git.Repo(str(expanded_path), search_parent_directories=True)
24
+ # Update repository and get detailed results
25
+ result = update_repository(repo, allow_password_prompt=allow_password_prompt, auto_sync=True)
26
+
27
+ # Keep track of repos with dependency changes for additional uv sync
28
+ repo_path = None
29
+ if result["dependencies_changed"] and not result["uv_sync_ran"]:
30
+ repo_path = Path(repo.working_dir)
31
+
32
+ return result, repo_path
33
+
34
+ except Exception as ex:
35
+ # Create a result for failed repos
36
+ error_result: RepositoryUpdateResult = {
37
+ "repo_path": str(expanded_path),
38
+ "status": "error",
39
+ "had_uncommitted_changes": False,
40
+ "uncommitted_files": [],
41
+ "commit_before": "",
42
+ "commit_after": "",
43
+ "commits_changed": False,
44
+ "pyproject_changed": False,
45
+ "dependencies_changed": False,
46
+ "uv_sync_ran": False,
47
+ "uv_sync_success": False,
48
+ "remotes_processed": [],
49
+ "remotes_skipped": [],
50
+ "error_message": str(ex),
51
+ "is_machineconfig_repo": False,
52
+ "permissions_updated": False,
53
+ }
54
+ console.print(
55
+ Panel(
56
+ "\n".join(
57
+ [
58
+ f"❌ Repository error: {expanded_path}",
59
+ f"Exception: {ex}",
60
+ ]
61
+ ),
62
+ border_style="red",
63
+ padding=(1, 2),
64
+ )
65
+ )
66
+ return error_result, None
67
+
68
+
19
69
  def _display_summary(results: list[RepositoryUpdateResult]) -> None:
20
70
  """Display a comprehensive summary of all repository update operations."""
21
71
 
@@ -148,7 +198,7 @@ def _display_summary(results: list[RepositoryUpdateResult]) -> None:
148
198
  def main(verbose: bool = True, allow_password_prompt: bool = False) -> None:
149
199
  """Main function to update all configured repositories."""
150
200
  _ = verbose
151
- repos: list[Path] = [Path.home() / "code/machineconfig"]
201
+ repos: list[Path] = []
152
202
  try:
153
203
  tmp = read_ini(DEFAULTS_PATH)["general"]["repos"].split(",")
154
204
  if tmp[-1] == "":
@@ -189,54 +239,23 @@ def main(verbose: bool = True, allow_password_prompt: bool = False) -> None:
189
239
  )
190
240
  )
191
241
 
192
- # Process repositories
242
+ # Process repositories in parallel
193
243
  results: list[RepositoryUpdateResult] = []
194
244
  repos_with_changes = []
195
245
 
196
- for expanded_path in repos:
197
- try:
198
- repo = git.Repo(str(expanded_path), search_parent_directories=True)
199
- # Update repository and get detailed results
200
- result = update_repository(repo, allow_password_prompt=allow_password_prompt, auto_sync=True)
246
+ with ThreadPoolExecutor(max_workers=min(len(repos), 8)) as executor:
247
+ # Submit all tasks
248
+ future_to_repo = {
249
+ executor.submit(_process_single_repo, expanded_path, allow_password_prompt): expanded_path
250
+ for expanded_path in repos
251
+ }
252
+
253
+ # Collect results as they complete
254
+ for future in as_completed(future_to_repo):
255
+ result, repo_path = future.result()
201
256
  results.append(result)
202
-
203
- # Keep track of repos with dependency changes for additional uv sync
204
- if result["dependencies_changed"] and not result["uv_sync_ran"]:
205
- repos_with_changes.append(Path(repo.working_dir))
206
-
207
- except Exception as ex:
208
- # Create a result for failed repos
209
- error_result: RepositoryUpdateResult = {
210
- "repo_path": str(expanded_path),
211
- "status": "error",
212
- "had_uncommitted_changes": False,
213
- "uncommitted_files": [],
214
- "commit_before": "",
215
- "commit_after": "",
216
- "commits_changed": False,
217
- "pyproject_changed": False,
218
- "dependencies_changed": False,
219
- "uv_sync_ran": False,
220
- "uv_sync_success": False,
221
- "remotes_processed": [],
222
- "remotes_skipped": [],
223
- "error_message": str(ex),
224
- "is_machineconfig_repo": False,
225
- "permissions_updated": False,
226
- }
227
- results.append(error_result)
228
- console.print(
229
- Panel(
230
- "\n".join(
231
- [
232
- f"❌ Repository error: {expanded_path}",
233
- f"Exception: {ex}",
234
- ]
235
- ),
236
- border_style="red",
237
- padding=(1, 2),
238
- )
239
- )
257
+ if repo_path is not None:
258
+ repos_with_changes.append(repo_path)
240
259
  # Run uv sync for repositories where pyproject.toml changed but sync wasn't run yet
241
260
  for repo_path in repos_with_changes:
242
261
  run_uv_sync(repo_path)
@@ -49,29 +49,30 @@ if ($selectedThemeName) {
49
49
  }
50
50
 
51
51
  # Read existing profile content or create empty array
52
- $profileContent = @()
53
52
  if (Test-Path $profilePath) {
54
- $profileContent = Get-Content $profilePath
53
+ $profileContent = Get-Content $profilePath -Raw
54
+ } else {
55
+ $profileContent = ""
55
56
  }
56
57
 
57
- # Check if oh-my-posh line already exists and replace it, or add it
58
- $found = $false
59
- for ($i = 0; $i -lt $profileContent.Count; $i++) {
60
- if ($profileContent[$i] -match "oh-my-posh init pwsh") {
61
- $profileContent[$i] = $ompLine
62
- $found = $true
63
- break
58
+ # Check if oh-my-posh line already exists and replace it
59
+ if ($profileContent -match "oh-my-posh init pwsh[^\r\n]*") {
60
+ # Replace existing oh-my-posh line
61
+ $profileContent = $profileContent -replace "oh-my-posh init pwsh[^\r\n]*", $ompLine
62
+ } else {
63
+ # Add the oh-my-posh line with proper newlines
64
+ if ($profileContent.Length -gt 0 -and -not $profileContent.EndsWith("`n")) {
65
+ $profileContent += "`n"
66
+ }
67
+ if ($profileContent.Length -gt 0) {
68
+ $profileContent += "`n"
64
69
  }
65
- }
66
-
67
- if (-not $found) {
68
- # Add the line at the end with a blank line before it
69
- $profileContent += ""
70
70
  $profileContent += $ompLine
71
+ $profileContent += "`n"
71
72
  }
72
73
 
73
74
  # Write back to profile
74
- $profileContent | Set-Content $profilePath -Encoding UTF8
75
+ $profileContent | Set-Content $profilePath -Encoding UTF8 -NoNewline
75
76
 
76
77
  Write-Host "Profile updated successfully!" -ForegroundColor Green
77
78
  Write-Host "The theme will be applied automatically in future PowerShell sessions." -ForegroundColor Cyan