machineconfig 5.21__py3-none-any.whl → 5.22__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.

@@ -13,7 +13,7 @@ from rich.panel import Panel
13
13
 
14
14
  system = platform.system()
15
15
  sep = ";" if system == "Windows" else ":" # PATH separator, this is special for PATH object, not to be confused with PathExtended.sep (normal paths), usually / or \
16
- PATH = os.environ["PATH"].split(sep) # this is a list of paths in PATH variable, not a crocodile.file_management.P object.
16
+ PATH = os.environ["PATH"].split(sep)
17
17
  console = Console()
18
18
  BOX_WIDTH = 100 # Define BOX_WIDTH or get it from a config
19
19
 
@@ -1,12 +1,9 @@
1
- """utils"""
2
-
3
- from pathlib import Path
1
+ import sys
4
2
  import git
5
3
  from machineconfig.utils.io import read_ini
6
4
  from machineconfig.utils.path_extended import PathExtended
7
5
  from machineconfig.utils.terminal import Response
8
6
 
9
- from machineconfig.scripts.python.helpers.repo_sync_helpers import fetch_dotfiles
10
7
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
11
8
  from machineconfig.utils.options import choose_from_options
12
9
  from machineconfig.utils.code import get_shell_file_executing_python_script, write_shell_script_to_file
@@ -15,13 +12,18 @@ import subprocess
15
12
  from typing import Optional, Literal
16
13
  from rich.console import Console
17
14
  from rich.panel import Panel
15
+ import typer
18
16
 
19
17
  console = Console()
20
18
 
21
- _ = fetch_dotfiles
22
-
23
19
 
24
- def main(cloud: Optional[str] = None, path: Optional[str] = None, message: Optional[str] = None, action: Literal["ask", "pushLocalMerge", "overwriteLocal", "InspectRepos", "RemoveLocalRclone"] = "ask", pwd: Optional[str] = None):
20
+ def main(
21
+ cloud: Optional[str] = typer.Option(None, "--cloud", "-c", help="Cloud storage profile name. If not provided, uses default from config."),
22
+ path: Optional[str] = typer.Option(None, "--path", "-p", help="Path to the local repository. Defaults to current working directory."),
23
+ message: Optional[str] = typer.Option(None, "--message", "-m", help="Commit message for local changes."),
24
+ on_conflict: Literal["ask", "pushLocalMerge", "overwriteLocal", "InspectRepos", "RemoveLocalRclone"] = typer.Option("ask", "--on-conflict", "-oc", help="Action to take on merge conflict. Default is 'ask'."),
25
+ pwd: Optional[str] = typer.Option(None, "--password", help="Password for encryption/decryption of the remote repository."),
26
+ ):
25
27
  if cloud is None:
26
28
  try:
27
29
  cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
@@ -31,8 +33,6 @@ def main(cloud: Optional[str] = None, path: Optional[str] = None, message: Optio
31
33
  return ""
32
34
  else:
33
35
  cloud_resolved = cloud
34
-
35
- # repo_root = PathExtended(args.repo).expanduser().absolute()
36
36
  repo_local_root = PathExtended.cwd() if path is None else PathExtended(path).expanduser().absolute()
37
37
  repo_local_obj = git.Repo(repo_local_root, search_parent_directories=True)
38
38
  repo_local_root = PathExtended(repo_local_obj.working_dir) # cwd might have been in a sub directory of repo_root, so its better to redefine it.
@@ -143,7 +143,7 @@ git commit -am "finished merging"
143
143
  print(f"• 4️⃣ {option4:75} 👉 {shell_file_4}")
144
144
 
145
145
  program_content = None
146
- match action:
146
+ match on_conflict:
147
147
  case "ask":
148
148
  choice = choose_from_options(multi=False, msg="Choose one option", options=[option1, option2, option3, option4], fzf=False)
149
149
  if choice == option1:
@@ -165,7 +165,7 @@ git commit -am "finished merging"
165
165
  case "RemoveLocalRclone":
166
166
  program_content = program_4
167
167
  case _:
168
- raise ValueError(f"Unknown action: {action}")
168
+ raise ValueError(f"Unknown action: {on_conflict}")
169
169
  # PROGRAM_PATH.write_text(program_content, encoding="utf-8")
170
170
  subprocess.run(program_content, shell=True, check=True)
171
171
 
@@ -173,17 +173,16 @@ git commit -am "finished merging"
173
173
 
174
174
 
175
175
  def args_parser():
176
- # console.print(Panel("🔄 Repository Synchronization Utility", title_align="left", border_style="blue"))
177
- # parser = argparse.ArgumentParser(description="Secure Repo CLI.")
178
- # parser.add_argument("path", nargs="?", type=str, help="Repository path, defaults to cwd.", default=None)
179
- # parser.add_argument("--cloud", "-c", help="rclone cloud profile name.", default=None)
180
- # parser.add_argument("--message", "-m", help="Commit Message", default=f"new message {randstr()}")
181
- # parser.add_argument("--pwd", "-p", help="Password for encryption", default=None)
182
- # parser.add_argument("--action", "-a", help="Action to take if merge fails.", choices=["ask", "pushLocalMerge", "overwriteLocal", "InspectRepos", "RemoveLocalRclone"], default="ask")
183
- # args = parser.parse_args()
184
- # main(cloud=args.cloud, path=args.path, message=args.message, action=args.action)
185
- import typer
186
- typer.run(main)
176
+ # Check if no arguments provided (excluding the script name)
177
+ if len(sys.argv) == 1:
178
+ app = typer.Typer(add_completion=False, help="Sync a local git repository with a remote encrypted cloud copy.")
179
+ app.command()(main)
180
+ app(["--help"])
181
+ return
182
+
183
+ app = typer.Typer(add_completion=False, no_args_is_help=True, help="Sync a local git repository with a remote encrypted cloud copy.")
184
+ app.command()(main)
185
+ app()
187
186
 
188
187
 
189
188
  if __name__ == "__main__":
@@ -20,10 +20,8 @@ console = Console()
20
20
 
21
21
  def add_print_header_pycode(path: str, title: str):
22
22
  return f"""
23
- try:
24
- from crocodile.file_management import P as PathExtended
25
- except ImportError:
26
- from machineconfig.utils.path_extended import PathExtended
23
+
24
+ from machineconfig.utils.path_extended import PathExtended
27
25
  pycode = PathExtended(r'{path}').read_text(encoding="utf-8")
28
26
  pycode = pycode.split("except Exception: print(pycode)")[2]
29
27
 
@@ -44,19 +44,24 @@ def interactive():
44
44
  @self_app.command()
45
45
  def status():
46
46
  """📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
47
- pass
47
+ import machineconfig.scripts.python.devops_status as helper
48
+ helper.main()
48
49
  @self_app.command()
49
50
  def clone():
50
- """📋 CLONE machienconfig locally for faster execution and nightly updates. """
51
+ """📋 CLONE machienconfig locally and incorporate to shell profile for faster execution and nightly updates."""
51
52
  import platform
52
53
  from machineconfig.utils.code import run_shell_script
54
+ from machineconfig.profile.shell import create_default_shell_profile
53
55
  if platform.system() == "Windows":
54
56
  from machineconfig.setup_windows import REPOS
57
+ create_default_shell_profile(method="copy")
55
58
  else:
56
59
  from machineconfig.setup_linux import REPOS
60
+ create_default_shell_profile(method="reference")
57
61
  run_shell_script(REPOS.read_text(encoding="utf-8"))
58
62
 
59
63
 
64
+
60
65
  @config_apps.command(no_args_is_help=True)
61
66
  def private(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for linking files"),
62
67
  on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
@@ -0,0 +1,521 @@
1
+ """Machine Status Display - Comprehensive system and configuration overview"""
2
+
3
+ import platform
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from machineconfig.utils.path_extended import PathExtended
14
+ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH, LIBRARY_ROOT
15
+
16
+
17
+ console = Console()
18
+
19
+
20
+ def _check_system_info() -> dict[str, str]:
21
+ """Gather basic system information."""
22
+ import socket
23
+ import os
24
+
25
+ return {
26
+ "hostname": socket.gethostname(),
27
+ "system": platform.system(),
28
+ "release": platform.release(),
29
+ "version": platform.version(),
30
+ "machine": platform.machine(),
31
+ "processor": platform.processor() or "Unknown",
32
+ "python_version": platform.python_version(),
33
+ "user": os.getenv("USER") or os.getenv("USERNAME") or "Unknown",
34
+ }
35
+
36
+
37
+ def _check_shell_profile_status() -> dict[str, Any]:
38
+ """Check shell profile configuration status."""
39
+ from machineconfig.profile.shell import get_shell_profile_path
40
+
41
+ try:
42
+ profile_path = get_shell_profile_path()
43
+ profile_exists = profile_path.exists()
44
+ profile_content = profile_path.read_text(encoding="utf-8") if profile_exists else ""
45
+
46
+ system_name = platform.system()
47
+ if system_name == "Windows":
48
+ init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/pwsh/init.ps1")
49
+ init_script_copy = PathExtended(CONFIG_PATH).joinpath("profile/init.ps1").collapseuser()
50
+ source_reference = f". {str(init_script.collapseuser()).replace('~', '$HOME')}"
51
+ source_copy = f". {str(init_script_copy).replace('~', '$HOME')}"
52
+ else:
53
+ init_script = PathExtended(LIBRARY_ROOT).joinpath("settings/shells/bash/init.sh")
54
+ init_script_copy = PathExtended(CONFIG_PATH).joinpath("profile/init.sh").collapseuser()
55
+ source_reference = f"source {str(init_script.collapseuser()).replace('~', '$HOME')}"
56
+ source_copy = f"source {str(init_script_copy).replace('~', '$HOME')}"
57
+
58
+ configured = source_reference in profile_content or source_copy in profile_content
59
+ method = "reference" if source_reference in profile_content else ("copy" if source_copy in profile_content else "none")
60
+
61
+ return {
62
+ "profile_path": str(profile_path),
63
+ "exists": profile_exists,
64
+ "configured": configured,
65
+ "method": method,
66
+ "init_script_exists": init_script.exists(),
67
+ "init_script_copy_exists": init_script_copy.exists(),
68
+ }
69
+ except Exception as ex:
70
+ return {"profile_path": "Error", "exists": False, "configured": False, "method": "error", "error": str(ex), "init_script_exists": False, "init_script_copy_exists": False}
71
+
72
+
73
+ def _check_machineconfig_repo() -> dict[str, Any]:
74
+ """Check machineconfig repository status."""
75
+ repo_path = Path.home().joinpath("code/machineconfig")
76
+ if not repo_path.exists():
77
+ return {"exists": False, "is_repo": False, "clean": False, "branch": "N/A", "commit": "N/A", "remotes": []}
78
+
79
+ try:
80
+ import git
81
+
82
+ repo = git.Repo(str(repo_path))
83
+ is_dirty = repo.is_dirty(untracked_files=True)
84
+ current_branch = repo.active_branch.name if not repo.head.is_detached else "DETACHED"
85
+ current_commit = repo.head.commit.hexsha[:8]
86
+ remotes = [remote.name for remote in repo.remotes]
87
+
88
+ return {
89
+ "exists": True,
90
+ "is_repo": True,
91
+ "clean": not is_dirty,
92
+ "branch": current_branch,
93
+ "commit": current_commit,
94
+ "remotes": remotes,
95
+ "path": str(repo_path),
96
+ }
97
+ except Exception as ex:
98
+ return {"exists": True, "is_repo": False, "clean": False, "branch": "Error", "commit": "N/A", "remotes": [], "error": str(ex)}
99
+
100
+
101
+ def _check_repos_status() -> dict[str, Any]:
102
+ """Check configured repositories status."""
103
+ from machineconfig.utils.io import read_ini
104
+
105
+ try:
106
+ repos_str = read_ini(DEFAULTS_PATH)["general"]["repos"]
107
+ repo_paths = [Path(p.strip()).expanduser() for p in repos_str.split(",") if p.strip()]
108
+
109
+ repos_info = []
110
+ for repo_path in repo_paths:
111
+ if not repo_path.exists():
112
+ repos_info.append({"path": str(repo_path), "name": repo_path.name, "exists": False, "is_repo": False})
113
+ continue
114
+
115
+ try:
116
+ import git
117
+
118
+ repo = git.Repo(str(repo_path))
119
+ repos_info.append({"path": str(repo_path), "name": repo_path.name, "exists": True, "is_repo": True, "clean": not repo.is_dirty(untracked_files=True), "branch": repo.active_branch.name if not repo.head.is_detached else "DETACHED"})
120
+ except Exception:
121
+ repos_info.append({"path": str(repo_path), "name": repo_path.name, "exists": True, "is_repo": False})
122
+
123
+ return {"configured": True, "count": len(repos_info), "repos": repos_info}
124
+ except (FileNotFoundError, KeyError, IndexError):
125
+ return {"configured": False, "count": 0, "repos": []}
126
+
127
+
128
+ def _check_ssh_status() -> dict[str, Any]:
129
+ """Check SSH configuration status."""
130
+ ssh_dir = PathExtended.home().joinpath(".ssh")
131
+ if not ssh_dir.exists():
132
+ return {"ssh_dir_exists": False, "keys": [], "config_exists": False, "authorized_keys_exists": False, "known_hosts_exists": False}
133
+
134
+ keys = []
135
+ for pub_key in ssh_dir.glob("*.pub"):
136
+ private_key = pub_key.with_suffix("")
137
+ keys.append({"name": pub_key.stem, "public_exists": True, "private_exists": private_key.exists(), "public_path": str(pub_key), "private_path": str(private_key)})
138
+
139
+ config_file = ssh_dir.joinpath("config")
140
+ authorized_keys = ssh_dir.joinpath("authorized_keys")
141
+ known_hosts = ssh_dir.joinpath("known_hosts")
142
+
143
+ return {
144
+ "ssh_dir_exists": True,
145
+ "keys": keys,
146
+ "config_exists": config_file.exists(),
147
+ "authorized_keys_exists": authorized_keys.exists(),
148
+ "known_hosts_exists": known_hosts.exists(),
149
+ "ssh_dir_path": str(ssh_dir),
150
+ }
151
+
152
+
153
+ def _check_config_files_status() -> dict[str, Any]:
154
+ """Check public and private configuration files status."""
155
+ from machineconfig.profile.create import read_mapper
156
+
157
+ try:
158
+ mapper = read_mapper()
159
+ public_configs = list(mapper.get("public", {}).keys())
160
+ private_configs = list(mapper.get("private", {}).keys())
161
+
162
+ public_count = len(public_configs)
163
+ private_count = len(private_configs)
164
+
165
+ public_linked = 0
166
+ for config_name in public_configs:
167
+ for config_item in mapper["public"][config_name]:
168
+ target_path = PathExtended(config_item["config_file_default_path"]).expanduser()
169
+ if target_path.exists():
170
+ public_linked += 1
171
+ break
172
+
173
+ private_linked = 0
174
+ for config_name in private_configs:
175
+ for config_item in mapper["private"][config_name]:
176
+ target_path = PathExtended(config_item["config_file_default_path"]).expanduser()
177
+ if target_path.exists():
178
+ private_linked += 1
179
+ break
180
+
181
+ return {
182
+ "public_count": public_count,
183
+ "public_linked": public_linked,
184
+ "private_count": private_count,
185
+ "private_linked": private_linked,
186
+ "public_configs": public_configs,
187
+ "private_configs": private_configs,
188
+ }
189
+ except Exception as ex:
190
+ return {"public_count": 0, "public_linked": 0, "private_count": 0, "private_linked": 0, "error": str(ex), "public_configs": [], "private_configs": []}
191
+
192
+
193
+ def _check_important_tools() -> dict[str, dict[str, bool]]:
194
+ """Check if important CLI tools are installed, organized by groups."""
195
+ from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
196
+
197
+ group_status = {}
198
+ for group_name, tools in PACKAGE_GROUP2NAMES.items():
199
+ tool_status = {}
200
+ for tool in tools:
201
+ tool_status[tool] = shutil.which(tool) is not None
202
+ group_status[group_name] = tool_status
203
+
204
+ return group_status
205
+
206
+
207
+ def _check_backup_config() -> dict[str, Any]:
208
+ """Check backup configuration status."""
209
+ from machineconfig.utils.io import read_ini
210
+ import tomllib
211
+
212
+ try:
213
+ cloud_config = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
214
+ except (FileNotFoundError, KeyError, IndexError):
215
+ cloud_config = "Not configured"
216
+
217
+ try:
218
+ backup_file = LIBRARY_ROOT.joinpath("profile/backup.toml")
219
+ if backup_file.exists():
220
+ backup_data = tomllib.loads(backup_file.read_text(encoding="utf-8"))
221
+ backup_items = list(backup_data.keys())
222
+ backup_items_count = len(backup_items)
223
+ else:
224
+ backup_items = []
225
+ backup_items_count = 0
226
+ except Exception:
227
+ backup_items = []
228
+ backup_items_count = 0
229
+
230
+ return {"cloud_config": cloud_config, "backup_items_count": backup_items_count, "backup_items": backup_items}
231
+
232
+
233
+ def _display_system_info(info: dict[str, str]) -> None:
234
+ """Display system information panel."""
235
+ console.rule("[bold blue]💻 System Information[/bold blue]")
236
+
237
+ table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
238
+ table.add_column("Property", style="cyan", no_wrap=True)
239
+ table.add_column("Value", style="white")
240
+
241
+ table.add_row("🏠 Hostname", info["hostname"])
242
+ table.add_row("💿 System", f"{info['system']} {info['release']}")
243
+ table.add_row("🖥️ Machine", info["machine"])
244
+ table.add_row("⚙️ Processor", info["processor"])
245
+ table.add_row("🐍 Python", info["python_version"])
246
+ table.add_row("👤 User", info["user"])
247
+
248
+ console.print(Panel(table, title="System", border_style="blue", padding=(1, 2), expand=False))
249
+
250
+
251
+ def _display_shell_status(status: dict[str, Any]) -> None:
252
+ """Display shell profile status panel."""
253
+ console.rule("[bold green]🐚 Shell Profile[/bold green]")
254
+
255
+ if "error" in status:
256
+ console.print(Panel(f"❌ Error: {status['error']}", title="Shell Profile", border_style="red", padding=(1, 2), expand=False))
257
+ return
258
+
259
+ from rich.columns import Columns
260
+
261
+ left_table = Table(show_header=False, box=None, padding=(0, 1))
262
+ left_table.add_column("Item", style="cyan", no_wrap=True)
263
+ left_table.add_column("Status")
264
+
265
+ left_table.add_row("📄 Profile", status['profile_path'])
266
+ left_table.add_row(f"{'✅' if status['exists'] else '❌'} Exists", str(status['exists']))
267
+ left_table.add_row(f"{'✅' if status['configured'] else '❌'} Configured", str(status['configured']))
268
+
269
+ right_table = Table(show_header=False, box=None, padding=(0, 1))
270
+ right_table.add_column("Item", style="cyan", no_wrap=True)
271
+ right_table.add_column("Status")
272
+
273
+ right_table.add_row("🔧 Method", status['method'])
274
+ right_table.add_row(f"{'✅' if status['init_script_exists'] else '❌'} Init (source)", str(status['init_script_exists']))
275
+ right_table.add_row(f"{'✅' if status['init_script_copy_exists'] else '❌'} Init (copy)", str(status['init_script_copy_exists']))
276
+
277
+ border_style = "green" if status["configured"] else "yellow"
278
+ console.print(Panel(Columns([left_table, right_table], equal=True, expand=True), title="Shell Profile", border_style=border_style, padding=(1, 2), expand=False))
279
+
280
+
281
+ def _display_machineconfig_repo(info: dict[str, Any]) -> None:
282
+ """Display machineconfig repository status."""
283
+ console.rule("[bold magenta]📦 Machineconfig Repository[/bold magenta]")
284
+
285
+ if not info["exists"]:
286
+ console.print(Panel("❌ Machineconfig repository not found at ~/code/machineconfig", title="Repository Status", border_style="red", padding=(1, 2), expand=False))
287
+ return
288
+
289
+ if not info["is_repo"]:
290
+ console.print(Panel(f"❌ Directory exists but is not a git repository\n{info.get('error', 'Unknown error')}", title="Repository Status", border_style="red", padding=(1, 2), expand=False))
291
+ return
292
+
293
+ table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
294
+ table.add_column("Property", style="cyan", no_wrap=True)
295
+ table.add_column("Value", style="white")
296
+
297
+ table.add_row("📁 Path", info['path'])
298
+ table.add_row("🌿 Branch", info['branch'])
299
+ table.add_row("🔖 Commit", info['commit'])
300
+ table.add_row(f"{'✅' if info['clean'] else '⚠️'} Status", 'Clean' if info['clean'] else 'Uncommitted changes')
301
+ table.add_row("📡 Remotes", ', '.join(info['remotes']) if info['remotes'] else 'None')
302
+
303
+ border_style = "green" if info["clean"] else "yellow"
304
+ console.print(Panel(table, title="Machineconfig Repository", border_style=border_style, padding=(1, 2), expand=False))
305
+
306
+
307
+ def _display_repos_status(status: dict[str, Any]) -> None:
308
+ """Display configured repositories status."""
309
+ console.rule("[bold cyan]📚 Configured Repositories[/bold cyan]")
310
+
311
+ if not status["configured"]:
312
+ console.print(Panel(f"⚠️ No repositories configured in {DEFAULTS_PATH}", title="Repositories", border_style="yellow", padding=(1, 2)))
313
+ return
314
+
315
+ if status["count"] == 0:
316
+ console.print(Panel("ℹ️ No repositories configured", title="Repositories", border_style="blue", padding=(1, 2)))
317
+ return
318
+
319
+ table = Table(show_lines=True, header_style="bold cyan")
320
+ table.add_column("Repository", style="bold")
321
+ table.add_column("Status")
322
+ table.add_column("Details")
323
+
324
+ for repo in status["repos"]:
325
+ name = repo["name"]
326
+ if not repo["exists"]:
327
+ table.add_row(f"❌ {name}", "Missing", f"Path: {repo['path']}")
328
+ elif not repo["is_repo"]:
329
+ table.add_row(f"⚠️ {name}", "Not a repo", f"Path: {repo['path']}")
330
+ else:
331
+ status_icon = "✅" if repo["clean"] else "⚠️"
332
+ status_text = "Clean" if repo["clean"] else "Uncommitted changes"
333
+ table.add_row(f"{status_icon} {name}", status_text, f"Branch: {repo['branch']}")
334
+
335
+ console.print(Panel(table, title=f"Repositories ({status['count']})", border_style="cyan", padding=(1, 2)))
336
+
337
+
338
+ def _display_ssh_status(status: dict[str, Any]) -> None:
339
+ """Display SSH configuration status."""
340
+ console.rule("[bold yellow]🔐 SSH Configuration[/bold yellow]")
341
+
342
+ if not status["ssh_dir_exists"]:
343
+ console.print(Panel("❌ SSH directory (~/.ssh) does not exist", title="SSH Status", border_style="red", padding=(1, 2), expand=False))
344
+ return
345
+
346
+ from rich.columns import Columns
347
+
348
+ config_table = Table(show_header=False, box=None, padding=(0, 1))
349
+ config_table.add_column("Item", style="cyan", no_wrap=True)
350
+ config_table.add_column("Status")
351
+
352
+ config_table.add_row("📁 Directory", status['ssh_dir_path'])
353
+ config_table.add_row(f"{'✅' if status['config_exists'] else '❌'} Config", str(status['config_exists']))
354
+ config_table.add_row(f"{'✅' if status['authorized_keys_exists'] else '❌'} Auth Keys", str(status['authorized_keys_exists']))
355
+ config_table.add_row(f"{'✅' if status['known_hosts_exists'] else '❌'} Known Hosts", str(status['known_hosts_exists']))
356
+
357
+ config_panel = Panel(config_table, title="SSH Config", border_style="yellow", padding=(1, 2), expand=False)
358
+
359
+ if status["keys"]:
360
+ keys_table = Table(show_header=True, box=None, padding=(0, 1), show_lines=False, expand=False)
361
+ keys_table.add_column("Key Name", style="bold cyan")
362
+ keys_table.add_column("Pub", justify="center")
363
+ keys_table.add_column("Priv", justify="center")
364
+
365
+ for key in status["keys"]:
366
+ pub_status = "✅" if key["public_exists"] else "❌"
367
+ priv_status = "✅" if key["private_exists"] else "❌"
368
+ keys_table.add_row(key["name"], pub_status, priv_status)
369
+
370
+ keys_panel = Panel(keys_table, title=f"SSH Keys ({len(status['keys'])})", border_style="yellow", padding=(1, 2), expand=False)
371
+
372
+ console.print(Columns([config_panel, keys_panel], equal=False, expand=True))
373
+ else:
374
+ console.print(config_panel)
375
+
376
+
377
+ def _display_config_files_status(status: dict[str, Any]) -> None:
378
+ """Display configuration files status."""
379
+ console.rule("[bold bright_blue]⚙️ Configuration Files[/bold bright_blue]")
380
+
381
+ if "error" in status:
382
+ console.print(Panel(f"❌ Error reading configuration: {status['error']}", title="Configuration Files", border_style="red", padding=(1, 2), expand=False))
383
+ return
384
+
385
+ public_percentage = (status["public_linked"] / status["public_count"] * 100) if status["public_count"] > 0 else 0
386
+ private_percentage = (status["private_linked"] / status["private_count"] * 100) if status["private_count"] > 0 else 0
387
+
388
+ table = Table(show_header=True, box=None, padding=(0, 2), expand=False)
389
+ table.add_column("Type", style="cyan", no_wrap=True)
390
+ table.add_column("Linked", justify="right")
391
+ table.add_column("Total", justify="right")
392
+ table.add_column("Progress", justify="right")
393
+
394
+ table.add_row("📂 Public", str(status['public_linked']), str(status['public_count']), f"{public_percentage:.0f}%")
395
+ table.add_row("🔒 Private", str(status['private_linked']), str(status['private_count']), f"{private_percentage:.0f}%")
396
+
397
+ overall_linked = status["public_linked"] + status["private_linked"]
398
+ overall_total = status["public_count"] + status["private_count"]
399
+ overall_percentage = (overall_linked / overall_total * 100) if overall_total > 0 else 0
400
+
401
+ border_style = "green" if overall_percentage > 80 else ("yellow" if overall_percentage > 50 else "red")
402
+
403
+ console.print(Panel(table, title=f"Configuration Files ({overall_percentage:.0f}% configured)", border_style=border_style, padding=(1, 2), expand=False))
404
+
405
+
406
+ def _display_tools_status(grouped_tools: dict[str, dict[str, bool]]) -> None:
407
+ """Display important tools installation status organized by groups."""
408
+ console.rule("[bold bright_magenta]🛠️ Important Tools[/bold bright_magenta]")
409
+
410
+ from rich.columns import Columns
411
+
412
+ all_group_panels = []
413
+ total_installed = 0
414
+ total_tools = 0
415
+
416
+ for group_name, tools in grouped_tools.items():
417
+ sorted_tools = sorted(tools.keys())
418
+ installed = [tool for tool, status in tools.items() if status]
419
+ total_installed += len(installed)
420
+ total_tools += len(tools)
421
+
422
+ num_columns = 8
423
+ tools_per_column = (len(sorted_tools) + num_columns - 1) // num_columns
424
+
425
+ tables = []
426
+ for col_idx in range(num_columns):
427
+ table = Table(show_header=False, box=None, padding=(0, 0), collapse_padding=True)
428
+ table.add_column("Tool", style="cyan", no_wrap=True, width=None)
429
+ table.add_column("", justify="center", width=2, no_wrap=True)
430
+
431
+ start_idx = col_idx * tools_per_column
432
+ end_idx = min(start_idx + tools_per_column, len(sorted_tools))
433
+
434
+ for i in range(start_idx, end_idx):
435
+ tool = sorted_tools[i]
436
+ status_icon = "✅" if tools[tool] else "❌"
437
+ table.add_row(tool, status_icon)
438
+
439
+ if start_idx < len(sorted_tools):
440
+ tables.append(table)
441
+
442
+ installed_percentage = (len(installed) / len(tools) * 100) if tools else 0
443
+ border_style = "green" if installed_percentage > 80 else ("yellow" if installed_percentage > 50 else "red")
444
+
445
+ group_display_name = group_name.replace("_", " ").title()
446
+ group_panel = Panel(
447
+ Columns(tables, equal=False, expand=False, padding=(0, 1)),
448
+ title=f"{group_display_name} ({len(installed)}/{len(tools)})",
449
+ border_style=border_style,
450
+ padding=(0, 1),
451
+ expand=False
452
+ )
453
+ all_group_panels.append(group_panel)
454
+
455
+ overall_percentage = (total_installed / total_tools * 100) if total_tools else 0
456
+ master_border_style = "green" if overall_percentage > 80 else ("yellow" if overall_percentage > 50 else "red")
457
+
458
+ from rich.console import Group
459
+ master_panel = Panel(
460
+ Group(*all_group_panels),
461
+ title=f"🛠️ Tools Overview ({total_installed}/{total_tools} installed - {overall_percentage:.0f}%)",
462
+ border_style=master_border_style,
463
+ padding=(1, 2),
464
+ expand=False
465
+ )
466
+ console.print(master_panel)
467
+
468
+
469
+ def _display_backup_status(status: dict[str, Any]) -> None:
470
+ """Display backup configuration status."""
471
+ console.rule("[bold bright_cyan]💾 Backup Configuration[/bold bright_cyan]")
472
+
473
+ table = Table(show_header=False, box=None, padding=(0, 1), expand=False)
474
+ table.add_column("Property", style="cyan", no_wrap=True)
475
+ table.add_column("Value", style="white")
476
+
477
+ table.add_row("🌥️ Cloud Config", status['cloud_config'])
478
+ table.add_row("📦 Backup Items", str(status['backup_items_count']))
479
+
480
+ border_style = "green" if status["cloud_config"] != "Not configured" else "yellow"
481
+
482
+ console.print(Panel(table, title="Backup Configuration", border_style=border_style, padding=(1, 2), expand=False))
483
+
484
+
485
+ def main() -> None:
486
+ """Main function to display comprehensive machine status."""
487
+ console.print("\n")
488
+ console.print(Panel(Text("📊 Machine Status Report", justify="center", style="bold white"), style="bold blue", padding=(1, 2)))
489
+ console.print("\n")
490
+
491
+ system_info = _check_system_info()
492
+ _display_system_info(system_info)
493
+
494
+ shell_status = _check_shell_profile_status()
495
+ _display_shell_status(shell_status)
496
+
497
+ machineconfig_repo = _check_machineconfig_repo()
498
+ _display_machineconfig_repo(machineconfig_repo)
499
+
500
+ repos_status = _check_repos_status()
501
+ _display_repos_status(repos_status)
502
+
503
+ ssh_status = _check_ssh_status()
504
+ _display_ssh_status(ssh_status)
505
+
506
+ config_status = _check_config_files_status()
507
+ _display_config_files_status(config_status)
508
+
509
+ tools_status = _check_important_tools()
510
+ _display_tools_status(tools_status)
511
+
512
+ backup_status = _check_backup_config()
513
+ _display_backup_status(backup_status)
514
+
515
+ console.print("\n")
516
+ console.print(Panel(Text("✨ Status report complete!", justify="center", style="bold green"), style="green", padding=(1, 2)))
517
+ console.print("\n")
518
+
519
+
520
+ if __name__ == "__main__":
521
+ main()
@@ -148,7 +148,7 @@ def _display_summary(results: list[RepositoryUpdateResult]) -> None:
148
148
  def main(verbose: bool = True, allow_password_prompt: bool = False) -> None:
149
149
  """Main function to update all configured repositories."""
150
150
  _ = verbose
151
- repos: list[Path] = [Path.home() / "code/machineconfig", Path.home() / "code/crocodile"]
151
+ repos: list[Path] = [Path.home() / "code/machineconfig"]
152
152
  try:
153
153
  tmp = read_ini(DEFAULTS_PATH)["general"]["repos"].split(",")
154
154
  if tmp[-1] == "":
@@ -24,7 +24,6 @@ class FireJobArgs:
24
24
  PathExport: bool = False
25
25
  git_pull: bool = False
26
26
  optimized: bool = False
27
- Nprocess: int = 1
28
27
  zellij_tab: Optional[str] = None
29
28
  watch: bool = False
30
29
 
@@ -96,6 +95,10 @@ def _convert_value_type(value: str) -> object:
96
95
  elif value.lower() in ('false', '0', 'no', 'off'):
97
96
  return False
98
97
 
98
+ # Try to convert None
99
+ if value.lower() == 'none':
100
+ return None
101
+
99
102
  # Try to parse as list (comma-separated values)
100
103
  if ',' in value:
101
104
  items = [_convert_value_type(item.strip()) for item in value.split(',')]
@@ -1,11 +1,7 @@
1
1
  from machineconfig.utils.path_extended import PathExtended
2
- from machineconfig.utils.terminal import Response
3
2
  from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
4
- from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
5
- from machineconfig.utils.io import read_ini
6
3
  from machineconfig.utils.code import write_shell_script_to_file
7
4
  import platform
8
- import subprocess
9
5
  from rich.console import Console
10
6
  from rich.panel import Panel
11
7
 
@@ -60,42 +56,6 @@ def inspect_repos(repo_local_root: str, repo_remote_root: str):
60
56
  raise NotImplementedError(f"Platform {platform.system()} not implemented.")
61
57
 
62
58
 
63
- def fetch_dotfiles():
64
- console.print(Panel("📁 Fetching Dotfiles", title="[bold blue]Dotfiles[/bold blue]", border_style="blue"))
65
-
66
- cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
67
- console.print(Panel(f"⚠️ Using default cloud: `{cloud_resolved}` from {DEFAULTS_PATH}", width=150, border_style="yellow"))
68
-
69
- dotfiles_local = PathExtended.home().joinpath("dotfiles")
70
- CONFIG_PATH.joinpath("remote").mkdir(parents=True, exist_ok=True)
71
- dotfiles_remote = PathExtended(CONFIG_PATH).joinpath("remote", dotfiles_local.rel2home())
72
- remote_path = dotfiles_local.get_remote_path(rel2home=True, os_specific=False, root="myhome") + ".zip.enc"
73
-
74
- console.print(Panel("📥 Downloading dotfiles from cloud...", width=150, border_style="blue"))
75
-
76
- dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved, unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
77
-
78
- console.print(Panel("🗑️ Removing old dotfiles and replacing with cloud version...", width=150, border_style="blue"))
79
-
80
- dotfiles_local.delete(sure=True)
81
- dotfiles_remote.move(folder=PathExtended.home())
82
- script = f"""
83
- # rm -rf {dotfiles_local}
84
- # mv {dotfiles_remote} {dotfiles_local}
85
- """
86
- if platform.system() == "Linux":
87
- script += """
88
- sudo chmod 600 $HOME/.ssh/*
89
- sudo chmod 700 $HOME/.ssh
90
- sudo chmod +x $HOME/dotfiles/scripts/linux -R
91
- """
92
- shell_path = write_shell_script_to_file(shell_script=script)
93
- completed = subprocess.run(f". {shell_path}", capture_output=True, check=False, text=True, shell=True)
94
- Response.from_completed_process(completed).capture().print()
95
-
96
- console.print(Panel("✅ Dotfiles successfully fetched and installed", title="[bold green]Dotfiles[/bold green]", border_style="green"))
97
-
98
-
99
59
  def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
100
60
  dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
101
61
  from git import Repo
@@ -6,7 +6,6 @@
6
6
  # # or use ots executable to do this job
7
7
 
8
8
  # import requests
9
- # import crocodile.file_management as fm
10
9
  # import base64
11
10
  # from typing import Optional
12
11
 
@@ -1,34 +1,6 @@
1
1
  #!/usr/bin/bash
2
2
 
3
3
 
4
- echo """
5
- #=======================================================================
6
- 🔄 REPOSITORIES SETUP | Cloning project codebases
7
- #=======================================================================
8
- """
9
-
10
- echo """📥 Setting up repositories...
11
- 🐊 crocodile - Main utility package
12
- 🔧 machineconfig - System configuration tools
13
- """
14
-
15
- mkdir -p $HOME/code
16
- cd $HOME/code
17
- # Setup crocodile repository
18
- if [ -d "crocodile" ]; then
19
- echo """🔄 crocodile directory exists, updating...
20
- """
21
- cd crocodile
22
- git reset --hard
23
- git pull
24
- cd ..
25
- else
26
- echo """⏳ Cloning crocodile repository...
27
- """
28
- git clone https://github.com/thisismygitrepo/crocodile.git --depth 4
29
- fi
30
-
31
- # Setup machineconfig repository
32
4
  cd $HOME/code
33
5
  if [ -d "machineconfig" ]; then
34
6
  echo """🔄 machineconfig directory exists, updating...
@@ -45,4 +17,4 @@ fi
45
17
 
46
18
  cd $HOME/code/machineconfig
47
19
  $HOME/.local/bin/uv sync --no-dev
48
- $HOME/.local/bin/uv cache clean
20
+ # $HOME/.local/bin/uv cache clean
@@ -9,18 +9,6 @@ if (-not (Get-Command git.exe -ErrorAction SilentlyContinue)) {
9
9
  $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
10
10
  }
11
11
 
12
- # Setup crocodile repository
13
- if (Test-Path "crocodile") {
14
- Write-Host "🔄 crocodile directory exists, updating..."
15
- Set-Location crocodile
16
- git reset --hard
17
- git pull
18
- Set-Location ..
19
- } else {
20
- Write-Host "⏳ Cloning crocodile repository..."
21
- git clone https://github.com/thisismygitrepo/crocodile.git --depth 4
22
- }
23
-
24
12
  # Setup machineconfig repository
25
13
  if (Test-Path "machineconfig") {
26
14
  Write-Host "🔄 machineconfig directory exists, updating..."
@@ -12,12 +12,10 @@ class Read:
12
12
  suffix = Path(path).suffix[1:]
13
13
  if suffix == "": raise ValueError(f"File type could not be inferred from suffix. Suffix is empty. Path: {path}")
14
14
  if suffix in ("sqlite", "sqlite3", "db", "duckdb"):
15
- # from crocodile.database import DBMS
16
- # if suffix == "duckdb": pass
17
- # res = DBMS.from_local_db(path=path)
18
- # print(res.describe_db())
19
- # return res
20
- raise NotImplementedError("Reading database files is not implemented yet. Use `crocodile.database.DBMS` to connect to the database file.")
15
+ from machineconfig.utils.files.dbms import DBMS
16
+ res = DBMS.from_local_db(path=path)
17
+ print(res.describe_db())
18
+ return res
21
19
  try: return getattr(Read, suffix)(str(path), **kwargs)
22
20
  except AttributeError as err:
23
21
  if "type object 'Read' has no attribute" not in str(err): raise AttributeError(err) from err
@@ -110,7 +110,7 @@ encryption = ssl
110
110
 
111
111
  def send_message(self, to: str, subject: str, body: str, txt_to_html: bool = True, attachments: Optional[list[Any]] = None):
112
112
  _ = attachments
113
- body += "\n\nThis is an automated email sent via crocodile.comms script."
113
+ body += "\n\nThis is an automated email sent via machineconfig.comms script."
114
114
  # msg = message.EmailMessage()
115
115
  msg = MIMEMultipart("alternative")
116
116
  msg["subject"] = subject
@@ -8,20 +8,9 @@ from machineconfig.utils.accessories import pprint
8
8
  # from machineconfig.utils.ve import get_ve_activate_line
9
9
 
10
10
 
11
- def get_header(wdir: OPLike, toolbox: bool):
12
- if toolbox:
13
- toobox_code = """
14
- try:
15
- from crocodile.toolbox import *
16
- except ImportError:
17
- print("Crocodile not found, skipping import.")
18
- pass
19
- """
20
- else:
21
- toobox_code = "# No toolbox import."
11
+ def get_header(wdir: OPLike):
22
12
  return f"""
23
13
  # >> Code prepended
24
- {toobox_code}
25
14
  {'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
26
15
  # >> End of header, start of script passed
27
16
  """
@@ -236,7 +225,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
236
225
  assert '"' not in cmd, 'Avoid using `"` in your command. I dont know how to handle this when passing is as command to python in pwsh command.'
237
226
  if not return_obj:
238
227
  return self.run(
239
- cmd=f"""uv run --no-dev --project $HOME/code/machineconfig -c "{get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"',
228
+ cmd=f"""uv run --no-dev --project $HOME/code/machineconfig -c "{get_header(wdir=None)}{cmd}\n""" + '"',
240
229
  desc=desc or f"run_py on {self.get_remote_repr()}",
241
230
  verbose=verbose,
242
231
  strict_err=strict_err,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 5.21
3
+ Version: 5.22
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -92,7 +92,7 @@ machineconfig/jobs/windows/msc/cli_agents.ps1,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
92
92
  machineconfig/profile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  machineconfig/profile/create.py,sha256=n8AEpwWrGmzyWyIcFxTXQp8Ztw6MTHTjt1aPlsw7yhc,13974
94
94
  machineconfig/profile/create_frontend.py,sha256=RlrQgsAzvJA3KQInPpavLJ6XIEayIjjq4it6dSLQR08,3352
95
- machineconfig/profile/shell.py,sha256=B2dg2eV9fPMlMLlqjL3W91E0ZrEEg5qpJXmBax-Kx_M,10958
95
+ machineconfig/profile/shell.py,sha256=H6Wae0KxcFuS88Vj8ZDXIp3YQr9Y6evK1DTUNrz4QPw,10871
96
96
  machineconfig/profile/records/generic/shares.toml,sha256=FduDztfyQtZcr5bfx-RSKhEEweweQSWfVXkKWnx8hCY,143
97
97
  machineconfig/profile/records/linux/apps_summary_report.csv,sha256=pw9djvaRUPalKDLn2sl3odcbD2_Zx3aEupsQ8UPfaaY,2738
98
98
  machineconfig/profile/records/linux/apps_summary_report.md,sha256=l77oofA6Rliql0ZgKGIZi8bstFoGyyGTxeS8p2PtOj0,5634
@@ -141,23 +141,24 @@ machineconfig/scripts/python/choose_wezterm_theme.py,sha256=Hlu_EOQhLM6wYdAdY25j
141
141
  machineconfig/scripts/python/cloud_copy.py,sha256=fcWbSo2nGiubtMYjGci8s5tVjZ9D-u8mteCawZmbw3I,8379
142
142
  machineconfig/scripts/python/cloud_manager.py,sha256=YN0DYLzPKtMBaks-EAVwFmkCu3XeHWMr1D21uqX5dDk,3429
143
143
  machineconfig/scripts/python/cloud_mount.py,sha256=GwcXbd5ohoHGESfX5edtCEl2-umDDxH_AZapmFSzc9E,6740
144
- machineconfig/scripts/python/cloud_repo_sync.py,sha256=8dnlHbQqRymPRU0v01pNIuaIvFeY4fReP7ewNSSCt34,9765
144
+ machineconfig/scripts/python/cloud_repo_sync.py,sha256=HtATW_pbd2j5M2g9dKFWaD_jq6GENX7KsCevVE6TGT8,9638
145
145
  machineconfig/scripts/python/cloud_sync.py,sha256=RWGpAfJ9fnN18yNBSgN44dzA38Hmd4879JL5r2pcyrM,3514
146
146
  machineconfig/scripts/python/count_lines.py,sha256=ZexMRsV70pe9fhLbGuens9EP5gCf078EwTDRHRZo5A0,15960
147
147
  machineconfig/scripts/python/count_lines_frontend.py,sha256=HlzPLU9_oJYqPNbnoQ0Hm4CuYy1UUlkZPcE5tFBSEbo,545
148
- machineconfig/scripts/python/croshell.py,sha256=zHUhOqWG81AOTeawZoDkpURnV1fAisY2lyZ0apvlmVY,6547
149
- machineconfig/scripts/python/devops.py,sha256=zPLNigp8jeINJUS6THHLOLQb1Rp9WTtXgYoZkLWI_ao,7053
148
+ machineconfig/scripts/python/croshell.py,sha256=LgmwB17EVgNgRkXgwLOZPQWrHqSYDo7Kd-d6vkqA3PY,6459
149
+ machineconfig/scripts/python/devops.py,sha256=VWdoSf0MxMl1e-egKn4mAm_gQ43OFDwhS8PCXZy3l3M,7341
150
150
  machineconfig/scripts/python/devops_add_identity.py,sha256=wvjNgqsLmqD2SxbNCW_usqfp0LI-TDvcJJKGOWt2oFw,3775
151
151
  machineconfig/scripts/python/devops_add_ssh_key.py,sha256=BXB-9RvuSZO0YTbnM2azeABW2ngLW4SKhhAGAieMzfw,6873
152
152
  machineconfig/scripts/python/devops_backup_retrieve.py,sha256=JLJHmi8JmZ_qVTeMW-qBEAYGt1fmfWXzZ7Gm-Q-GDcU,5585
153
- machineconfig/scripts/python/devops_update_repos.py,sha256=X5_qUlEj8O8qgk9hcXs45krP6_c9D1rC2StGrPjpCp8,9425
153
+ machineconfig/scripts/python/devops_status.py,sha256=qSmDCNopKq8DLcx3u1sLhCIZtILP2ZzdGYZuA7fvrJ8,22487
154
+ machineconfig/scripts/python/devops_update_repos.py,sha256=9p21ckEfqyiCoLWgNgkE1RSswb6b9YPeCf5lmBtudsQ,9393
154
155
  machineconfig/scripts/python/dotfile.py,sha256=9W9i8Qbs6i2NfTq0knywB3StvE_sHaZYZ0RslTyoVz8,2734
155
156
  machineconfig/scripts/python/fire_agents_help_launch.py,sha256=1ymWiszfjCyPv3ofinWzfOmbzLEt3d7ntac_afLh-V4,5017
156
157
  machineconfig/scripts/python/fire_agents_help_search.py,sha256=qIfSS_su2YJ1Gb0_lu4cbjlJlYMBw0v52NTGiSrGjk8,2991
157
158
  machineconfig/scripts/python/fire_agents_helper_types.py,sha256=zKu8Vr6iucaGSkCm_Tkt_WrYU7-6Nript3coYyzTXzY,295
158
159
  machineconfig/scripts/python/fire_agents_load_balancer.py,sha256=mpqx3uaQdBXYieuvhdK-qsvLepf9oIMo3pwPj9mSEDI,1079
159
160
  machineconfig/scripts/python/fire_jobs.py,sha256=r_iJbjdktxpgtQV7N4Smc1MMw6ssYUByIlQDNyIrW5A,13413
160
- machineconfig/scripts/python/fire_jobs_args_helper.py,sha256=-gl5I-26Op12nToIpAA-YEpvn8MMkNlI5XjhUbj50I0,4273
161
+ machineconfig/scripts/python/fire_jobs_args_helper.py,sha256=UUrGB2N_pR7PxFKtKTJxIUiS58WjQX0U50y2ft8Ul4w,4334
161
162
  machineconfig/scripts/python/fire_jobs_route_helper.py,sha256=9zGuh_bMkQgfMS0nnFoa2oIWdmLAkSNtlEH4H-FprmM,5373
162
163
  machineconfig/scripts/python/fire_jobs_streamlit_helper.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
163
164
  machineconfig/scripts/python/ftpx.py,sha256=QfQTp-6jQP6yxfbLc5sKxiMtTgAgc8sjN7d17_uLiZc,9400
@@ -167,7 +168,7 @@ machineconfig/scripts/python/interactive.py,sha256=FYb-bhn5GYDSER_l_w8YS_LwEI1ae
167
168
  machineconfig/scripts/python/mount_nfs.py,sha256=aECrL64j9g-9rF49sVJAjGmzaoGgcMnl3g9v17kQF4c,3239
168
169
  machineconfig/scripts/python/mount_nw_drive.py,sha256=iru6AtnTyvyuk6WxlK5R4lDkuliVpPV5_uBTVVhXtjQ,1550
169
170
  machineconfig/scripts/python/mount_ssh.py,sha256=k2fKq3f5dKq_7anrFOlqvJoI_3U4EWNHLRZ1o3Lsy6M,2268
170
- machineconfig/scripts/python/onetimeshare.py,sha256=bmGsNnskym5OWfIhpOfZG5jq3m89FS0a6dF5Sb8LaZM,2539
171
+ machineconfig/scripts/python/onetimeshare.py,sha256=xRd8by6qUm-od2Umty2MYsXyJwzXw-CBTd7VellNaKY,2498
171
172
  machineconfig/scripts/python/pomodoro.py,sha256=SPkfeoZGv8rylGiOyzQ7UK3aXZ3G2FIOuGkSuBUggOI,2019
172
173
  machineconfig/scripts/python/repos.py,sha256=7OwnQVedmLVgDnIY-OJ9epa1AcmRoUptu8ihyRNe_u4,5105
173
174
  machineconfig/scripts/python/repos_helper.py,sha256=3jLdnNf1canpzi3JXiz5VA6UTUmLeNHuhjOWVl_thP0,3006
@@ -219,7 +220,7 @@ machineconfig/scripts/python/helpers/cloud_helpers.py,sha256=GA-bxXouUmknk9fyQAs
219
220
  machineconfig/scripts/python/helpers/helpers2.py,sha256=2QeQ2aii6hVc4S-oi3SVTSyPxKPTDUWBD7GnkCEr7Qs,7304
220
221
  machineconfig/scripts/python/helpers/helpers4.py,sha256=iKR5vVJygaDIpFXhcdma9jOpyxKtUhmqcmalFxJmY0w,4749
221
222
  machineconfig/scripts/python/helpers/helpers5.py,sha256=dPBvA9Tcyx9TMgM6On49A1CueGMhBdRzikDnlJGf3J0,1123
222
- machineconfig/scripts/python/helpers/repo_sync_helpers.py,sha256=7n9feSfWw1p0Nie-uWg_NTe_IiYRUjeCvaNxsZyV13s,5432
223
+ machineconfig/scripts/python/helpers/repo_sync_helpers.py,sha256=okgsaY_Cc5wEGuUzoR9sKsioN5s8vnYOstOURQVKyBI,3461
223
224
  machineconfig/scripts/windows/agents.ps1,sha256=DqdrC_Xc2rwQ6kGzT0xh5CJz4B_0p5ZwB7s8XE6rPCM,77
224
225
  machineconfig/scripts/windows/choose_wezterm_theme.ps1,sha256=LiXJ0a4LKjb6E-oH_bAg6DjegV4SqDUdiMp_svGCFlI,95
225
226
  machineconfig/scripts/windows/cloud_copy.ps1,sha256=llTFhN2uInZTcoZYZuuhJcf5Ifo5MF226I5MpOzvc3A,82
@@ -361,7 +362,7 @@ machineconfig/setup_linux/__init__.py,sha256=acmXv9KPYt9jdPuLGp6O392r-hSdzayyjn-
361
362
  machineconfig/setup_linux/apps.sh,sha256=0PdPojpj2B-wOxE6ryvguKwBvL6vEt-43A1NdnwJumM,3198
362
363
  machineconfig/setup_linux/apps_desktop.sh,sha256=nBt_24tbdgKYK68w15ZiktgcEEJ7ytLoPWIrDluTkIQ,5077
363
364
  machineconfig/setup_linux/apps_gui.sh,sha256=ufsCqmShc8G3XyKBmchD1tJ85FZYjSDY2yiE_0Rl7v4,3031
364
- machineconfig/setup_linux/repos.sh,sha256=kPBhhBmUAgB_9xwBQGyoL6_m0E4mlonnSqTEzi2gH9Q,1215
365
+ machineconfig/setup_linux/repos.sh,sha256=VHrMMadbu__P94eF5SRpy1OeXm1fNjAxFJjITioxKXQ,476
365
366
  machineconfig/setup_linux/ve.sh,sha256=yHxDzRoiAkEmLvUHNHUZksyOWpwiirLJruM_tgsuwYs,579
366
367
  machineconfig/setup_linux/nix/cli_installation.sh,sha256=AQ_wRmldeD1tPqCmU7qgz9ZrZFly4OYwBJDGRpb9IJ0,5470
367
368
  machineconfig/setup_linux/others/mint_keyboard_shortcuts.sh,sha256=F5dbg0n9RHsKGPn8fIdZMn3p0RrHEkb8rWBGsdVGbus,1207
@@ -371,7 +372,7 @@ machineconfig/setup_linux/web_shortcuts/android.sh,sha256=gzep6bBhK7FCBvGcXK0fdJ
371
372
  machineconfig/setup_linux/web_shortcuts/interactive.sh,sha256=l0ULuMUhidth8ksOeZatw5alrny0zRN5toL5MUQzRMQ,468
372
373
  machineconfig/setup_windows/__init__.py,sha256=M8DFE3a4BW7-EAH9-F0F6U3WfYMF8wiLfXf9f3Mv2Go,438
373
374
  machineconfig/setup_windows/apps.ps1,sha256=G5GqZ9G0aiQr_A-HaahtRdzpaTTdW6n3DRKMZWDTSPc,11214
374
- machineconfig/setup_windows/repos.ps1,sha256=_JBVfW4UZPM3iGIIXjHr7e7f8RkDm4Zpu2uceFV_xtc,1257
375
+ machineconfig/setup_windows/repos.ps1,sha256=gIQBOLIh65oUXgSjYMeYeD6lU1Bu80LZ59xqRc3T3BA,918
375
376
  machineconfig/setup_windows/ve.ps1,sha256=a0Vk7BJfp_dY5GpDYC20WrJbqZHIWWbCZ9hDpKobMDk,311
376
377
  machineconfig/setup_windows/others/docker.ps1,sha256=M8NfsSxH8YlmY92J4rSe1xWOwTW8IFrdgb8cI8Riu2E,311
377
378
  machineconfig/setup_windows/others/obs.ps1,sha256=2andchcXpxS3rqZjGaMpY5VShxTAKWvw6eCrayjuaLo,30
@@ -389,7 +390,7 @@ machineconfig/utils/code.py,sha256=CaDMxAoOKkjeMCr0Zw-yH0ghfz-4yvf3q7dPHKvHzrg,5
389
390
  machineconfig/utils/installer.py,sha256=FitnRR2xOekkAygXgptKWdHHGdbXImKR8PcbgPmiPXw,10738
390
391
  machineconfig/utils/io.py,sha256=ZXB3aataS1IZ_0WMcCRSmoN1nbkvEO-bWYcs-TpngqU,2872
391
392
  machineconfig/utils/links.py,sha256=GQExBsMoxewOhwIrNdERuzk9HVKcmWgNUGO-RzPMS6M,22588
392
- machineconfig/utils/notifications.py,sha256=vvdsY5IX6XEiILTnt5lNyHxhCi0ljdGX2T_67VRfrG4,9009
393
+ machineconfig/utils/notifications.py,sha256=tuXIudcip0tEioG-bm8BbLr3FMDve4f6BktlznBhKxM,9013
393
394
  machineconfig/utils/options.py,sha256=vUO4Kej-vDOv64wHr2HNDyu6PATURpjd7xp6N8OOoJg,7083
394
395
  machineconfig/utils/path_extended.py,sha256=Xjdn2AVnB8p1jfNMNe2kJutVa5zGnFFJVGZbw-Bp_hg,53200
395
396
  machineconfig/utils/path_helper.py,sha256=0e3Xh3BAEv27oqcezNeVLHJllGmLEgLH4T1l90m-650,8014
@@ -397,7 +398,7 @@ machineconfig/utils/procs.py,sha256=w75oGKfR7FpT1pGTGd2XscnEOO0IHBWxohLbi69hLqg,
397
398
  machineconfig/utils/scheduler.py,sha256=GbprwuxoJYdtkCsg7JZPXM8un9Z7v9tPjUoQjtS0oHU,14955
398
399
  machineconfig/utils/scheduling.py,sha256=RF1iXJpqf4Dg18jdZWtBixz97KAHC6VKYqTFSpdLWuc,11188
399
400
  machineconfig/utils/source_of_truth.py,sha256=GnjcVkKm11RyZFHGnPbne5YDEBYoZ5yryBNkpfGC7O4,854
400
- machineconfig/utils/ssh.py,sha256=KTUp42nT4Vuh350_EvArQoR2N5iVXrc2BFld93fRUX4,20919
401
+ machineconfig/utils/ssh.py,sha256=wIoIdeeTYCLvfdtFojUrKTlMRKUdf-eMqYae6g915w0,20666
401
402
  machineconfig/utils/terminal.py,sha256=IlmOByfQG-vjhaFFxxzU5rWzP5_qUzmalRfuey3PAmc,11801
402
403
  machineconfig/utils/upgrade_packages.py,sha256=H96zVJEWXJW07nh5vhjuSCrPtXGqoUb7xeJsFYYdmCI,3330
403
404
  machineconfig/utils/ve.py,sha256=L-6PBXnQGXThiwWgheJMQoisAZOZA6SVCbGw2J-GFnI,2414
@@ -408,7 +409,7 @@ machineconfig/utils/cloud/onedrive/transaction.py,sha256=m-aNcnWj_gfZVvJOSpkdIqj
408
409
  machineconfig/utils/files/ascii_art.py,sha256=cNJaJC07vx94fS44-tzgfbfBeCwXVrgpnWGBLUnfC38,5212
409
410
  machineconfig/utils/files/dbms.py,sha256=xJTg6H_AvZWsduuDaSs4_KdUu__rO8LXrE_hrcXTgNM,17203
410
411
  machineconfig/utils/files/headers.py,sha256=L54G11DfLadyZGyQXSQ7y8UI_tNvlld7zqP4qEAWL88,3647
411
- machineconfig/utils/files/read.py,sha256=hNeHAqnTLewrIyGZZwEYHjnBtof0x2GyKOhqsrSA6TM,4750
412
+ machineconfig/utils/files/read.py,sha256=VGraV3_73gGFwqYZ-GJzsfERsmO21n2JkFLKvck4fmc,4561
412
413
  machineconfig/utils/installer_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
413
414
  machineconfig/utils/installer_utils/github_release_bulk.py,sha256=WJf_qZlF02SmIc6C7o1h4Gy4gAaJAfeAS8O9s2Itj-k,6535
414
415
  machineconfig/utils/installer_utils/installer.py,sha256=H9wukQBSZDLlpB6Xn4Sa2hSV8iCUr9UQChcgOa3u-L8,9278
@@ -418,8 +419,8 @@ machineconfig/utils/schemas/fire_agents/fire_agents_input.py,sha256=pTxvLzIpD5RF
418
419
  machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoSpdmTIdgS9LS-RvE-QZ-D260tD3o,1214
419
420
  machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
420
421
  machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
421
- machineconfig-5.21.dist-info/METADATA,sha256=o9KOmpOSq1ZegBwvgyFbvyhqp-dt4dJHpSPzYqnignE,8030
422
- machineconfig-5.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
423
- machineconfig-5.21.dist-info/entry_points.txt,sha256=2afE1mw-o4MUlfxyX73SV02XaQI4SV_LdL2r6_CzhPU,1074
424
- machineconfig-5.21.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
425
- machineconfig-5.21.dist-info/RECORD,,
422
+ machineconfig-5.22.dist-info/METADATA,sha256=xLortdqCUVyhyJucZzbnC87ptXeuApjvaHsLd_3i1Zw,8030
423
+ machineconfig-5.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
424
+ machineconfig-5.22.dist-info/entry_points.txt,sha256=2afE1mw-o4MUlfxyX73SV02XaQI4SV_LdL2r6_CzhPU,1074
425
+ machineconfig-5.22.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
426
+ machineconfig-5.22.dist-info/RECORD,,