machineconfig 5.24__py3-none-any.whl → 5.26__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 (48) hide show
  1. machineconfig/jobs/installer/custom/hx.py +1 -1
  2. machineconfig/jobs/installer/custom_dev/alacritty.py +4 -4
  3. machineconfig/jobs/installer/linux_scripts/wezterm.sh +1 -1
  4. machineconfig/scripts/python/agents.py +4 -4
  5. machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
  6. machineconfig/scripts/python/devops.py +6 -6
  7. machineconfig/scripts/python/{devops_update_repos.py → devops_helpers/devops_update_repos.py} +1 -1
  8. machineconfig/scripts/python/fire_jobs.py +3 -3
  9. machineconfig/scripts/python/helpers/repo_sync_helpers.py +14 -1
  10. machineconfig/scripts/python/helpers_fire/__init__.py +0 -0
  11. machineconfig/scripts/python/{fire_agents_help_launch.py → helpers_fire/fire_agents_help_launch.py} +1 -1
  12. machineconfig/scripts/python/helpers_fire_command/__init__.py +0 -0
  13. machineconfig/scripts/python/helpers_fire_command/fire_jobs_streamlit_helper.py +0 -0
  14. machineconfig/scripts/python/helpers_repos/grource.py +341 -0
  15. machineconfig/scripts/python/interactive.py +11 -19
  16. machineconfig/scripts/python/repos.py +75 -73
  17. machineconfig/scripts/python/{count_lines_frontend.py → repos_helpers/count_lines_frontend.py} +1 -1
  18. machineconfig/scripts/python/{repos_helper.py → repos_helpers/repos_helper.py} +4 -12
  19. machineconfig/scripts/python/{repos_helper_action.py → repos_helpers/repos_helper_action.py} +1 -1
  20. machineconfig/scripts/python/sessions_multiprocess.py +1 -1
  21. machineconfig/utils/files/ouch/__init__.py +0 -0
  22. machineconfig/utils/files/ouch/decompress.py +45 -0
  23. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
  24. machineconfig/utils/source_of_truth.py +0 -1
  25. machineconfig/utils/ssh.py +33 -19
  26. {machineconfig-5.24.dist-info → machineconfig-5.26.dist-info}/METADATA +3 -5
  27. {machineconfig-5.24.dist-info → machineconfig-5.26.dist-info}/RECORD +46 -42
  28. machineconfig/scripts/python/get_zellij_cmd.py +0 -15
  29. machineconfig/scripts/python/t4.py +0 -17
  30. /machineconfig/jobs/{python → installer}/check_installations.py +0 -0
  31. /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → devops_helpers/__init__.py} +0 -0
  32. /machineconfig/scripts/python/{devops_add_identity.py → devops_helpers/devops_add_identity.py} +0 -0
  33. /machineconfig/scripts/python/{devops_add_ssh_key.py → devops_helpers/devops_add_ssh_key.py} +0 -0
  34. /machineconfig/scripts/python/{devops_backup_retrieve.py → devops_helpers/devops_backup_retrieve.py} +0 -0
  35. /machineconfig/scripts/python/{devops_status.py → devops_helpers/devops_status.py} +0 -0
  36. /machineconfig/scripts/python/{fire_agents_help_search.py → helpers_fire/fire_agents_help_search.py} +0 -0
  37. /machineconfig/scripts/python/{fire_agents_helper_types.py → helpers_fire/fire_agents_helper_types.py} +0 -0
  38. /machineconfig/scripts/python/{fire_agents_load_balancer.py → helpers_fire/fire_agents_load_balancer.py} +0 -0
  39. /machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -0
  40. /machineconfig/scripts/python/{fire_jobs_args_helper.py → helpers_fire_command/fire_jobs_args_helper.py} +0 -0
  41. /machineconfig/scripts/python/{fire_jobs_route_helper.py → helpers_fire_command/fire_jobs_route_helper.py} +0 -0
  42. /machineconfig/scripts/python/{count_lines.py → repos_helpers/count_lines.py} +0 -0
  43. /machineconfig/scripts/python/{repos_helper_clone.py → repos_helpers/repos_helper_clone.py} +0 -0
  44. /machineconfig/scripts/python/{repos_helper_record.py → repos_helpers/repos_helper_record.py} +0 -0
  45. /machineconfig/scripts/python/{repos_helper_update.py → repos_helpers/repos_helper_update.py} +0 -0
  46. {machineconfig-5.24.dist-info → machineconfig-5.26.dist-info}/WHEEL +0 -0
  47. {machineconfig-5.24.dist-info → machineconfig-5.26.dist-info}/entry_points.txt +0 -0
  48. {machineconfig-5.24.dist-info → machineconfig-5.26.dist-info}/top_level.txt +0 -0
@@ -77,7 +77,7 @@ def main(version: Optional[str], install_lib: bool = False):
77
77
  print(f" ✨ Cleaned '{runtime_path}' and '{contrib_path}'.")
78
78
 
79
79
  print("\n📦 [Step 4/5] Installing Helix components...")
80
- target_config_dir = PathExtended("~/.config/helix").expanduser()
80
+ target_config_dir = PathExtended.home().joinpath(".config/helix").expanduser()
81
81
  target_config_dir.mkdir(parents=True, exist_ok=True)
82
82
 
83
83
  if platform.system() in ["Linux", "Darwin"]:
@@ -30,8 +30,8 @@ def main(installer_data: InstallerData, version: Optional[str]) -> None:
30
30
  program = """
31
31
 
32
32
  cargo install alacritty
33
- mkdir -p ~/.config/alacritty/themes
34
- git clone https://github.com/alacritty/alacritty-theme ~/.config/alacritty/themes
33
+ mkdir -p $HOME/.config/alacritty/themes
34
+ git clone https://github.com/alacritty/alacritty-theme $HOME/.config/alacritty/themes
35
35
 
36
36
  """
37
37
  elif platform.system() in ["Linux", "Darwin"]:
@@ -41,8 +41,8 @@ git clone https://github.com/alacritty/alacritty-theme ~/.config/alacritty/theme
41
41
 
42
42
 
43
43
  cargo install alacritty
44
- mkdir -p ~/.config/alacritty/themes
45
- git clone https://github.com/alacritty/alacritty-theme ~/.config/alacritty/themes
44
+ mkdir -p $HOME/.config/alacritty/themes
45
+ git clone https://github.com/alacritty/alacritty-theme $HOME/.config/alacritty/themes
46
46
 
47
47
  """
48
48
  else:
@@ -36,5 +36,5 @@ echo """#=======================================================================
36
36
  #=======================================================================
37
37
  """
38
38
  echo "🚀 You can now launch WezTerm from your applications menu or by typing 'wezterm' in terminal"
39
- echo "💡 Configure WezTerm by editing ~/.config/wezterm/wezterm.lua"
39
+ echo "💡 Configure WezTerm by editing $HOME/.config/wezterm/wezterm.lua"
40
40
  echo "🔗 For more information and configuration options, visit: https://wezfurlong.org/wezterm/"
@@ -5,7 +5,7 @@
5
5
  from pathlib import Path
6
6
  from typing import cast, Iterable, Optional, get_args
7
7
  import typer
8
- from machineconfig.scripts.python.fire_agents_helper_types import AGENTS
8
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import AGENTS
9
9
 
10
10
 
11
11
  def _write_list_file(target: Path, files: Iterable[Path]) -> None:
@@ -28,9 +28,9 @@ def create(
28
28
  agents_dir: Optional[Path] = typer.Option(None, help="Directory to store agent files. If not provided, will be constructed automatically."),
29
29
  ):
30
30
 
31
- from machineconfig.scripts.python.fire_agents_help_launch import prep_agent_launch, get_agents_launch_layout
32
- from machineconfig.scripts.python.fire_agents_help_search import search_files_by_pattern, search_python_files
33
- from machineconfig.scripts.python.fire_agents_load_balancer import chunk_prompts
31
+ from machineconfig.scripts.python.helpers_fire.fire_agents_help_launch import prep_agent_launch, get_agents_launch_layout
32
+ from machineconfig.scripts.python.helpers_fire.fire_agents_help_search import search_files_by_pattern, search_python_files
33
+ from machineconfig.scripts.python.helpers_fire.fire_agents_load_balancer import chunk_prompts
34
34
  from machineconfig.utils.accessories import get_repo_root, randstr
35
35
  import json
36
36
 
@@ -52,14 +52,14 @@ def main2():
52
52
 
53
53
  def set_theme(theme: str):
54
54
  print(f"🔄 Setting WezTerm theme to: {theme}")
55
- txt_lines = PathExtended("~/.config/wezterm/wezterm.lua").expanduser().read_text(encoding="utf-8").splitlines()
55
+ txt_lines = PathExtended.home().joinpath(".config/wezterm/wezterm.lua").expanduser().read_text(encoding="utf-8").splitlines()
56
56
  res_lines = []
57
57
  for line in txt_lines:
58
58
  if "config.color_scheme = " in line:
59
59
  res_lines.append(f"config.color_scheme = '{theme}'")
60
60
  else:
61
61
  res_lines.append(line)
62
- PathExtended("~/.config/wezterm/wezterm.lua").expanduser().write_text("\n".join(res_lines), encoding="utf-8")
62
+ PathExtended.home().joinpath(".config/wezterm/wezterm.lua").expanduser().write_text("\n".join(res_lines), encoding="utf-8")
63
63
  time.sleep(0.1)
64
64
  print("💾 Configuration saved")
65
65
 
@@ -34,7 +34,7 @@ app.add_typer(self_app, name="self")
34
34
  @self_app.command()
35
35
  def update():
36
36
  """🔄 UPDATE essential repos"""
37
- import machineconfig.scripts.python.devops_update_repos as helper
37
+ import machineconfig.scripts.python.devops_helpers.devops_update_repos as helper
38
38
  helper.main()
39
39
  @self_app.command()
40
40
  def interactive():
@@ -44,7 +44,7 @@ def interactive():
44
44
  @self_app.command()
45
45
  def status():
46
46
  """📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
47
- import machineconfig.scripts.python.devops_status as helper
47
+ import machineconfig.scripts.python.devops_helpers.devops_status as helper
48
48
  helper.main()
49
49
  @self_app.command()
50
50
  def clone():
@@ -100,12 +100,12 @@ def shell(method: Annotated[Literal["copy", "reference"], typer.Argument(help="C
100
100
  @nw_apps.command()
101
101
  def add_key():
102
102
  """🔑 SSH add pub key to this machine"""
103
- import machineconfig.scripts.python.devops_add_ssh_key as helper
103
+ import machineconfig.scripts.python.devops_helpers.devops_add_ssh_key as helper
104
104
  helper.main()
105
105
  @nw_apps.command()
106
106
  def add_identity():
107
107
  """🗝️ SSH add identity (private key) to this machine"""
108
- import machineconfig.scripts.python.devops_add_identity as helper
108
+ import machineconfig.scripts.python.devops_helpers.devops_add_identity as helper
109
109
  helper.main()
110
110
  @nw_apps.command()
111
111
  def connect():
@@ -131,14 +131,14 @@ def setup():
131
131
  @app_data.command()
132
132
  def backup():
133
133
  """💾 BACKUP"""
134
- from machineconfig.scripts.python.devops_backup_retrieve import main_backup_retrieve
134
+ from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
135
135
  main_backup_retrieve(direction="BACKUP")
136
136
 
137
137
 
138
138
  @app_data.command()
139
139
  def retrieve():
140
140
  """📥 RETRIEVE"""
141
- from machineconfig.scripts.python.devops_backup_retrieve import main_backup_retrieve
141
+ from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
142
142
  main_backup_retrieve(direction="RETRIEVE")
143
143
 
144
144
 
@@ -8,7 +8,7 @@ from rich.panel import Panel
8
8
  from rich.table import Table
9
9
  from rich.text import Text
10
10
 
11
- from machineconfig.scripts.python.repos_helper_update import RepositoryUpdateResult, run_uv_sync, update_repository
11
+ from machineconfig.scripts.python.repos_helpers.repos_helper_update import RepositoryUpdateResult, run_uv_sync, update_repository
12
12
  from machineconfig.utils.io import read_ini
13
13
  from machineconfig.utils.source_of_truth import DEFAULTS_PATH
14
14
 
@@ -12,7 +12,7 @@ from machineconfig.utils.options import choose_from_options
12
12
  from machineconfig.utils.path_helper import match_file_name, sanitize_path
13
13
  from machineconfig.utils.path_extended import PathExtended
14
14
  from machineconfig.utils.accessories import get_repo_root, randstr
15
- from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs, extract_kwargs, parse_fire_args_from_context
15
+ from machineconfig.scripts.python.helpers_fire_command.fire_jobs_args_helper import FireJobArgs, extract_kwargs, parse_fire_args_from_context
16
16
 
17
17
  import platform
18
18
  from typing import Optional, Annotated
@@ -55,13 +55,13 @@ def route(args: FireJobArgs, fire_args: str = "") -> None:
55
55
  # ========================= choosing function to run
56
56
  choice_function: Optional[str] = None # Initialize to avoid unbound variable
57
57
  if args.choose_function:
58
- from machineconfig.scripts.python.fire_jobs_route_helper import choose_function_or_lines
58
+ from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import choose_function_or_lines
59
59
  choice_function, choice_file, kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict)
60
60
  else:
61
61
  choice_function = args.function
62
62
 
63
63
  if choice_file.suffix == ".py":
64
- from machineconfig.scripts.python.fire_jobs_route_helper import get_command_streamlit
64
+ from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import get_command_streamlit
65
65
  if args.streamlit: exe = get_command_streamlit(choice_file, args.environment, repo_root)
66
66
  elif args.interactive is False: exe = "python"
67
67
  elif args.jupyter: exe = "jupyter-lab"
@@ -1,13 +1,26 @@
1
1
  from machineconfig.utils.path_extended import PathExtended
2
- from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
3
2
  from machineconfig.utils.code import write_shell_script_to_file
4
3
  import platform
4
+ from pathlib import Path
5
5
  from rich.console import Console
6
6
  from rich.panel import Panel
7
7
 
8
8
  console = Console()
9
9
 
10
10
 
11
+ def get_zellij_cmd(wd1: Path, wd2: Path) -> str:
12
+ _ = wd1, wd2
13
+ lines = [
14
+ """ zellij action new-tab --name gitdiff""",
15
+ """zellij action new-pane --direction down --name local --cwd ./data """,
16
+ """zellij action write-chars "cd '{wd1}'; git status" """,
17
+ """zellij action move-focus up; zellij action close-pane """,
18
+ """zellij action new-pane --direction down --name remote --cwd code """,
19
+ """zellij action write-chars "cd '{wd2}' """,
20
+ """git status" """,
21
+ ]
22
+ return "; ".join(lines)
23
+
11
24
  def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cloud: str):
12
25
  console.print(Panel("🗑️ Deleting remote repo copy and pushing local copy", title="[bold blue]Repo Sync[/bold blue]", border_style="blue"))
13
26
  repo_sync_root = PathExtended(remote_repo).expanduser().absolute()
File without changes
@@ -2,7 +2,7 @@
2
2
  import random
3
3
  import shlex
4
4
  from pathlib import Path
5
- from machineconfig.scripts.python.fire_agents_helper_types import AGENTS, AGENT_NAME_FORMATTER
5
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import AGENTS, AGENT_NAME_FORMATTER
6
6
 
7
7
 
8
8
  def get_gemini_api_keys() -> list[str]:
@@ -0,0 +1,341 @@
1
+ """Gource visualization tool for git repositories."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated, Optional
5
+ import subprocess
6
+ import platform
7
+ import zipfile
8
+ import typer
9
+
10
+
11
+ def get_gource_install_dir() -> Path:
12
+ """Get the installation directory for portable Gource."""
13
+ if platform.system() == "Windows":
14
+ appdata = Path.home() / "AppData" / "Local"
15
+ return appdata / "gource"
16
+ else:
17
+ return Path.home() / ".local" / "bin" / "gource"
18
+
19
+
20
+ def get_gource_executable() -> Path:
21
+ """Get the path to the gource executable (inside the extracted directory with DLLs)."""
22
+ install_dir = get_gource_install_dir()
23
+ if platform.system() == "Windows":
24
+ possible_paths = [
25
+ install_dir / "gource.exe",
26
+ install_dir / f"gource-{get_default_version()}.win64" / "gource.exe",
27
+ ]
28
+ for path in possible_paths:
29
+ if path.exists():
30
+ return path
31
+ return install_dir / f"gource-{get_default_version()}.win64" / "gource.exe"
32
+ else:
33
+ return install_dir / "gource"
34
+
35
+
36
+ def get_default_version() -> str:
37
+ """Get the default gource version."""
38
+ return "0.53"
39
+
40
+
41
+ def install_gource_windows(version: Optional[str] = None) -> None:
42
+ """Install portable Gource on Windows by downloading and extracting the zip archive."""
43
+ if platform.system() != "Windows":
44
+ raise OSError(f"This installer is for Windows only. Current OS: {platform.system()}")
45
+
46
+ from machineconfig.utils.path_extended import PathExtended
47
+ from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR
48
+
49
+ print("\n" + "=" * 80)
50
+ print("🚀 GOURCE PORTABLE INSTALLATION 🚀")
51
+ print("=" * 80 + "\n")
52
+
53
+ version_str = version or get_default_version()
54
+ portable_url = f"https://github.com/acaudwell/Gource/releases/download/gource-{version_str}/gource-{version_str}.win64.zip"
55
+ install_dir = get_gource_install_dir()
56
+
57
+ print(f"📥 Downloading portable Gource from: {portable_url}")
58
+ downloaded_zip = PathExtended(portable_url).download(folder=INSTALL_TMP_DIR)
59
+ print(f"✅ Downloaded to: {downloaded_zip}")
60
+
61
+ print(f"\n� Extracting to: {install_dir}")
62
+ install_dir.mkdir(parents=True, exist_ok=True)
63
+
64
+ try:
65
+ with zipfile.ZipFile(downloaded_zip, 'r') as zip_ref:
66
+ zip_ref.extractall(install_dir)
67
+ print(f"✅ Extracted successfully to: {install_dir}")
68
+ print(f" (The zip contains gource-{version_str}.win64/ directory with exe and DLL dependencies)")
69
+ except Exception as e:
70
+ print(f"❌ Extraction failed with error: {e}")
71
+ raise
72
+
73
+ print("\n🗑️ Cleaning up zip file...")
74
+ try:
75
+ downloaded_zip.unlink()
76
+ print(f"✅ Removed zip file: {downloaded_zip}")
77
+ except Exception as e:
78
+ print(f"⚠️ Warning: Could not remove zip file: {e}")
79
+
80
+ gource_exe = get_gource_executable()
81
+ if gource_exe.exists():
82
+ print(f"\n✅ Gource executable found at: {gource_exe}")
83
+ dll_dir = gource_exe.parent
84
+ dll_count = len(list(dll_dir.glob("*.dll")))
85
+ print(f" Found {dll_count} DLL dependencies in: {dll_dir}")
86
+ else:
87
+ print(f"\n⚠️ Warning: Expected executable not found at: {gource_exe}")
88
+ print(f" Contents of {install_dir}:")
89
+ for item in install_dir.rglob("*"):
90
+ if item.is_file():
91
+ print(f" - {item.relative_to(install_dir)}")
92
+
93
+ print("\n" + "=" * 80)
94
+ print("✅ GOURCE PORTABLE INSTALLATION COMPLETED")
95
+ print("=" * 80)
96
+ print(f"\n📌 Gource installed to: {install_dir}")
97
+ print(f" Executable: {gource_exe}")
98
+ print(" All DLL dependencies are kept together in the same directory.")
99
+ print(" This script will automatically use the portable version.")
100
+
101
+
102
+ def visualize(
103
+ repo: Annotated[str, typer.Option("--repo", "-r", help="Path to git repository to visualize")] = Path.cwd().__str__(),
104
+ output_file: Annotated[Optional[Path], typer.Option("--output", "-o", help="Output video file (e.g., output.mp4). If specified, gource will render to video.")] = None,
105
+ resolution: Annotated[str, typer.Option("--resolution", "-res", help="Video resolution (e.g., 1920x1080, 1280x720)")] = "1920x1080",
106
+ seconds_per_day: Annotated[float, typer.Option("--seconds-per-day", "-spd", help="Speed of simulation (lower = faster)")] = 0.1,
107
+ auto_skip_seconds: Annotated[float, typer.Option("--auto-skip-seconds", "-as", help="Skip to next entry if nothing happens for X seconds")] = 1.0,
108
+ title: Annotated[Optional[str], typer.Option("--title", "-t", help="Title for the visualization")] = None,
109
+ hide_items: Annotated[list[str], typer.Option("--hide", "-h", help="Items to hide: bloom, date, dirnames, files, filenames, mouse, progress, root, tree, users, usernames")] = [],
110
+ key_items: Annotated[bool, typer.Option("--key", "-k", help="Show file extension key")] = False,
111
+ fullscreen: Annotated[bool, typer.Option("--fullscreen", "-f", help="Run in fullscreen mode")] = False,
112
+ viewport: Annotated[Optional[str], typer.Option("--viewport", "-v", help="Camera viewport (e.g., '1000x1000')")] = None,
113
+ start_date: Annotated[Optional[str], typer.Option("--start-date", help="Start date (YYYY-MM-DD)")] = None,
114
+ stop_date: Annotated[Optional[str], typer.Option("--stop-date", help="Stop date (YYYY-MM-DD)")] = None,
115
+ user_image_dir: Annotated[Optional[Path], typer.Option("--user-image-dir", help="Directory with user avatar images")] = None,
116
+ max_files: Annotated[int, typer.Option("--max-files", help="Maximum number of files to show (0 = no limit)")] = 0,
117
+ max_file_lag: Annotated[float, typer.Option("--max-file-lag", help="Max time files remain on screen after last change")] = 5.0,
118
+ file_idle_time: Annotated[int, typer.Option("--file-idle-time", help="Time in seconds files remain idle before being removed")] = 0,
119
+ framerate: Annotated[int, typer.Option("--framerate", help="Frames per second for video output")] = 60,
120
+ background_color: Annotated[str, typer.Option("--background-color", help="Background color in hex (e.g., 000000 for black)")] = "000000",
121
+ font_size: Annotated[int, typer.Option("--font-size", help="Font size")] = 22,
122
+ camera_mode: Annotated[str, typer.Option("--camera-mode", help="Camera mode: overview or track")] = "overview",
123
+ ) -> None:
124
+ """
125
+ Visualize git repository history using Gource with reasonable defaults.
126
+
127
+ Examples:
128
+ # Basic visualization of current directory
129
+ python grource.py visualize
130
+
131
+ # Visualize specific repository
132
+ python grource.py visualize --repo-path /path/to/repo
133
+
134
+ # Create video output
135
+ python grource.py visualize --output output.mp4
136
+
137
+ # Fast visualization with custom title
138
+ python grource.py visualize --seconds-per-day 0.01 --title "My Project"
139
+
140
+ # Hide specific elements
141
+ python grource.py visualize --hide filenames --hide date
142
+
143
+ # Custom resolution and viewport
144
+ python grource.py visualize --resolution 2560x1440 --viewport 1200x1200
145
+ """
146
+ print("\n" + "=" * 80)
147
+ print("🎬 GOURCE VISUALIZATION 🎬")
148
+ print("=" * 80 + "\n")
149
+ repo_path: Path = Path(repo).expanduser().resolve()
150
+ if not repo_path.exists():
151
+ print(f"❌ Error: Repository path does not exist: {repo_path}")
152
+ raise typer.Exit(1)
153
+
154
+ if not repo_path.joinpath(".git").exists():
155
+ print(f"❌ Error: Not a git repository: {repo_path}")
156
+ raise typer.Exit(1)
157
+
158
+ print(f"📁 Repository: {repo_path}")
159
+ print("⚙️ Configuration:")
160
+ print(f" - Resolution: {resolution}")
161
+ print(f" - Speed: {seconds_per_day} seconds per day")
162
+ print(f" - Auto-skip: {auto_skip_seconds} seconds")
163
+ if output_file:
164
+ print(f" - Output: {output_file}")
165
+ print()
166
+
167
+ gource_exe: Path = get_gource_executable()
168
+ if not gource_exe.exists():
169
+ if platform.system() == "Windows":
170
+ print(f"⚠️ Portable gource not found at {gource_exe}, installing...")
171
+ install_gource_windows()
172
+ # Check again after installation
173
+ if gource_exe.exists():
174
+ print(f"✅ Gource installed successfully at: {gource_exe}")
175
+ gource_cmd: str = str(gource_exe)
176
+ else:
177
+ print("❌ Installation failed, falling back to system gource")
178
+ gource_cmd = "gource"
179
+ else:
180
+ gource_cmd = "gource"
181
+ print(f"⚠️ Portable gource not found at {gource_exe}, using system gource")
182
+ else:
183
+ gource_cmd = str(gource_exe)
184
+
185
+ cmd: list[str] = [gource_cmd, str(repo_path)]
186
+
187
+ cmd.extend(["--seconds-per-day", str(seconds_per_day)])
188
+ cmd.extend(["--auto-skip-seconds", str(auto_skip_seconds)])
189
+
190
+ if resolution:
191
+ width, height = resolution.split("x")
192
+ cmd.extend(["-{}x{}".format(width, height)])
193
+
194
+ if title:
195
+ cmd.extend(["--title", title])
196
+ elif not title and not output_file:
197
+ cmd.extend(["--title", repo_path.name])
198
+
199
+ for hide_item in hide_items:
200
+ cmd.extend(["--hide", hide_item])
201
+
202
+ if key_items:
203
+ cmd.append("--key")
204
+
205
+ if fullscreen and not output_file:
206
+ cmd.append("--fullscreen")
207
+
208
+ if viewport:
209
+ cmd.extend(["--viewport", viewport])
210
+
211
+ if start_date:
212
+ cmd.extend(["--start-date", start_date])
213
+
214
+ if stop_date:
215
+ cmd.extend(["--stop-date", stop_date])
216
+
217
+ if user_image_dir and user_image_dir.exists():
218
+ cmd.extend(["--user-image-dir", str(user_image_dir)])
219
+
220
+ if max_files > 0:
221
+ cmd.extend(["--max-files", str(max_files)])
222
+
223
+ cmd.extend(["--max-file-lag", str(max_file_lag)])
224
+
225
+ if file_idle_time > 0:
226
+ cmd.extend(["--file-idle-time", str(file_idle_time)])
227
+
228
+ cmd.extend(["--background-colour", background_color])
229
+ cmd.extend(["--font-size", str(font_size)])
230
+ cmd.extend(["--camera-mode", camera_mode])
231
+
232
+ if output_file:
233
+ cmd.extend(["-r", str(framerate)])
234
+ if platform.system() == "Windows":
235
+ cmd.extend(["-o", "-"])
236
+ ffmpeg_cmd: list[str] = [
237
+ "ffmpeg",
238
+ "-y",
239
+ "-r", str(framerate),
240
+ "-f", "image2pipe",
241
+ "-vcodec", "ppm",
242
+ "-i", "-",
243
+ "-vcodec", "libx264",
244
+ "-preset", "medium",
245
+ "-pix_fmt", "yuv420p",
246
+ "-crf", "23",
247
+ str(output_file),
248
+ ]
249
+ print("🎥 Rendering video...")
250
+ print(f" Command: {' '.join(cmd)} | {' '.join(ffmpeg_cmd)}")
251
+ print()
252
+ try:
253
+ gource_proc: subprocess.Popen[bytes] = subprocess.Popen(cmd, stdout=subprocess.PIPE)
254
+ ffmpeg_proc: subprocess.Popen[bytes] = subprocess.Popen(ffmpeg_cmd, stdin=gource_proc.stdout)
255
+ if gource_proc.stdout:
256
+ gource_proc.stdout.close()
257
+ ffmpeg_proc.communicate()
258
+ print(f"\n✅ Video saved to: {output_file}")
259
+ except subprocess.CalledProcessError as e:
260
+ print(f"❌ Error during video rendering: {e}")
261
+ raise typer.Exit(1)
262
+ except FileNotFoundError:
263
+ print("❌ Error: ffmpeg not found. Please install ffmpeg to create video output.")
264
+ print(" Download from: https://ffmpeg.org/download.html")
265
+ raise typer.Exit(1)
266
+ else:
267
+ cmd.extend(["-o", "-"])
268
+ ffmpeg_cmd = [
269
+ "ffmpeg",
270
+ "-y",
271
+ "-r", str(framerate),
272
+ "-f", "image2pipe",
273
+ "-vcodec", "ppm",
274
+ "-i", "-",
275
+ "-vcodec", "libx264",
276
+ "-preset", "medium",
277
+ "-pix_fmt", "yuv420p",
278
+ "-crf", "23",
279
+ str(output_file),
280
+ ]
281
+ print("🎥 Rendering video...")
282
+ print(f" Command: {' '.join(cmd)} | {' '.join(ffmpeg_cmd)}")
283
+ print()
284
+ try:
285
+ gource_proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
286
+ ffmpeg_proc = subprocess.Popen(ffmpeg_cmd, stdin=gource_proc.stdout)
287
+ if gource_proc.stdout:
288
+ gource_proc.stdout.close()
289
+ ffmpeg_proc.communicate()
290
+ print(f"\n✅ Video saved to: {output_file}")
291
+ except subprocess.CalledProcessError as e:
292
+ print(f"❌ Error during video rendering: {e}")
293
+ raise typer.Exit(1)
294
+ except FileNotFoundError:
295
+ print("❌ Error: ffmpeg not found. Please install ffmpeg to create video output.")
296
+ raise typer.Exit(1)
297
+ else:
298
+ print("🎬 Launching interactive visualization...")
299
+ print(f" Command: {' '.join(cmd)}")
300
+ print()
301
+ try:
302
+ subprocess.run(cmd, check=True)
303
+ except subprocess.CalledProcessError as e:
304
+ print(f"❌ Error running gource: {e}")
305
+ raise typer.Exit(1)
306
+ except FileNotFoundError:
307
+ print("❌ Error: gource not found. Please install gource first.")
308
+ if platform.system() == "Windows":
309
+ print(" Run: uv run python src/machineconfig/scripts/python/grource.py install")
310
+ else:
311
+ print(" For Linux/Mac, use your package manager:")
312
+ print(" - Ubuntu/Debian: sudo apt install gource")
313
+ print(" - macOS: brew install gource")
314
+ print(" - Fedora: sudo dnf install gource")
315
+ raise typer.Exit(1)
316
+
317
+ print("\n" + "=" * 80)
318
+ print("✅ VISUALIZATION COMPLETED")
319
+ print("=" * 80)
320
+
321
+
322
+ def install(
323
+ version: Optional[str] = typer.Option("0.53", "--version", "-v", help="Gource version to install"),
324
+ ) -> None:
325
+ """Install portable Gource on Windows (no admin privileges required)."""
326
+ if platform.system() == "Windows":
327
+ install_gource_windows(version=version)
328
+ else:
329
+ print(f"❌ Portable installer currently supports Windows only. Current OS: {platform.system()}")
330
+ print("For Linux/Mac, please use your package manager:")
331
+ print(" - Ubuntu/Debian: sudo apt install gource")
332
+ print(" - macOS: brew install gource")
333
+ print(" - Fedora: sudo dnf install gource")
334
+ raise typer.Exit(1)
335
+
336
+
337
+ if __name__ == "__main__":
338
+ app = typer.Typer(help="Gource visualization tool for git repositories")
339
+ app.command()(install)
340
+ app.command()(visualize)
341
+ app()
@@ -36,7 +36,6 @@ console = Console()
36
36
  def display_header() -> None:
37
37
  from machineconfig.utils.installer import get_machineconfig_version
38
38
  from rich.align import Align
39
- from rich.padding import Padding
40
39
 
41
40
  # Fancy ASCII art header
42
41
  ascii_art = """
@@ -57,23 +56,16 @@ def display_header() -> None:
57
56
  subtitle = "🎯 Your digital life manager. Dotfiles, data, code and more."
58
57
  bug_report = "🐛 Please report bugs to Alex Al-Saffar @ https://github.com/thisismygitrepo/machineconfig"
59
58
 
60
- # Create styled texts
61
- ascii_text = Text(ascii_art, style="bold cyan")
62
- title_text = Text(title, style="bold bright_magenta")
63
- subtitle_text = Text(subtitle, style="italic bright_blue")
64
- bug_text = Text(bug_report, style="dim white")
65
-
66
- # Create panel content with proper alignment
67
- with console.capture() as capture:
68
- console.print(ascii_text)
69
- console.print()
70
- console.print(Align.center(title_text))
71
- console.print(Align.center(subtitle_text))
72
- console.print()
73
- console.print(Align.center(bug_text))
74
-
75
- panel_content = capture.get()
76
- console.print(Panel(Padding(panel_content, (1, 2)), border_style="bright_cyan", padding=(1, 2)))
59
+ # Print ASCII art
60
+ console.print(Text(ascii_art, style="bold cyan"))
61
+ console.print()
62
+
63
+ # Print centered text elements
64
+ console.print(Align.center(Text(title, style="bold bright_magenta")))
65
+ console.print(Align.center(Text(subtitle, style="italic bright_blue")))
66
+ console.print()
67
+ console.print(Align.center(Text(bug_report, style="dim white")))
68
+ console.print()
77
69
  def display_completion_message() -> None:
78
70
  completion_text = Text("INSTALLATION COMPLETE", style="bold green")
79
71
  subtitle_text = Text("System setup finished successfully", style="italic green")
@@ -190,7 +182,7 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
190
182
  console.print(Panel("💾 [bold bright_cyan]DATA RETRIEVAL[/bold bright_cyan]\n[italic]Backup restoration[/italic]", border_style="bright_cyan"))
191
183
  console.print("🔧 Retrieving backup data", style="bold cyan")
192
184
  try:
193
- from machineconfig.scripts.python.devops_backup_retrieve import main_backup_retrieve
185
+ from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
194
186
  main_backup_retrieve(direction="RETRIEVE")
195
187
  console.print("✅ Backup data retrieved successfully", style="bold green")
196
188
  except Exception as e: