machineconfig 7.57__py3-none-any.whl → 7.79__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 (104) hide show
  1. machineconfig/cluster/sessions_managers/utils/maker.py +21 -9
  2. machineconfig/jobs/installer/custom/boxes.py +2 -2
  3. machineconfig/jobs/installer/custom/hx.py +3 -3
  4. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  5. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
  6. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -1
  7. machineconfig/jobs/installer/custom_dev/sysabc.py +36 -28
  8. machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
  9. machineconfig/jobs/installer/installer_data.json +127 -25
  10. machineconfig/jobs/installer/package_groups.py +20 -13
  11. machineconfig/profile/create_links_export.py +2 -2
  12. machineconfig/scripts/__init__.py +0 -4
  13. machineconfig/scripts/linux/wrap_mcfg +1 -1
  14. machineconfig/scripts/python/agents.py +22 -17
  15. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +3 -0
  16. machineconfig/scripts/python/croshell.py +22 -17
  17. machineconfig/scripts/python/devops.py +3 -4
  18. machineconfig/scripts/python/devops_navigator.py +0 -4
  19. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  20. machineconfig/scripts/python/fire_jobs.py +19 -18
  21. machineconfig/scripts/python/ftpx.py +36 -12
  22. machineconfig/scripts/python/helpers/ast_search.py +74 -0
  23. machineconfig/scripts/python/helpers/qr_code.py +166 -0
  24. machineconfig/scripts/python/helpers/repo_rag.py +325 -0
  25. machineconfig/scripts/python/helpers/symantic_search.py +25 -0
  26. machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
  27. machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
  28. machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
  29. machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
  30. machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
  31. machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
  32. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +4 -5
  33. machineconfig/scripts/python/helpers_devops/cli_nw.py +88 -7
  34. machineconfig/scripts/python/helpers_devops/cli_self.py +7 -6
  35. machineconfig/scripts/python/helpers_devops/cli_share_file.py +9 -9
  36. machineconfig/scripts/python/helpers_devops/cli_share_server.py +13 -12
  37. machineconfig/scripts/python/helpers_devops/cli_terminal.py +7 -6
  38. machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -73
  39. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  40. machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
  41. machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -3
  42. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +23 -13
  43. machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
  44. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +7 -4
  45. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  46. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  47. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  48. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +5 -5
  49. machineconfig/scripts/python/helpers_utils/download.py +152 -0
  50. machineconfig/scripts/python/helpers_utils/path.py +81 -31
  51. machineconfig/scripts/python/interactive.py +2 -2
  52. machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
  53. machineconfig/scripts/python/msearch.py +21 -2
  54. machineconfig/scripts/python/nw/address.py +132 -0
  55. machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
  56. machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
  57. machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
  58. machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
  59. machineconfig/scripts/python/sessions.py +35 -20
  60. machineconfig/scripts/python/terminal.py +2 -2
  61. machineconfig/scripts/python/utils.py +12 -10
  62. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  63. machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
  64. machineconfig/settings/shells/nushell/config.nu +2 -2
  65. machineconfig/settings/shells/nushell/env.nu +45 -6
  66. machineconfig/settings/shells/nushell/init.nu +282 -95
  67. machineconfig/settings/shells/pwsh/init.ps1 +1 -0
  68. machineconfig/settings/shells/zsh/init.sh +0 -7
  69. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  70. machineconfig/setup_windows/uv.ps1 +8 -1
  71. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
  72. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
  73. machineconfig/utils/accessories.py +7 -4
  74. machineconfig/utils/code.py +6 -4
  75. machineconfig/utils/files/headers.py +2 -2
  76. machineconfig/utils/installer_utils/install_from_url.py +180 -0
  77. machineconfig/utils/installer_utils/installer_class.py +53 -47
  78. machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +71 -65
  79. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
  80. machineconfig/utils/links.py +2 -2
  81. machineconfig/utils/meta.py +30 -16
  82. machineconfig/utils/options.py +4 -4
  83. machineconfig/utils/path_extended.py +3 -3
  84. machineconfig/utils/path_helper.py +33 -31
  85. machineconfig/utils/schemas/layouts/layout_types.py +1 -1
  86. machineconfig/utils/ssh.py +143 -409
  87. machineconfig/utils/ssh_utils/abc.py +8 -0
  88. machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
  89. machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
  90. machineconfig/utils/ssh_utils/utils.py +141 -0
  91. machineconfig/utils/ssh_utils/wsl.py +168 -0
  92. machineconfig/utils/upgrade_packages.py +2 -1
  93. machineconfig/utils/ve.py +11 -4
  94. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/METADATA +1 -1
  95. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/RECORD +102 -92
  96. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/entry_points.txt +2 -2
  97. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  98. machineconfig/scripts/python/explore.py +0 -49
  99. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  100. /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
  101. /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
  102. /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
  103. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/WHEEL +0 -0
  104. {machineconfig-7.57.dist-info → machineconfig-7.79.dist-info}/top_level.txt +0 -0
@@ -2,16 +2,9 @@
2
2
  TODO: use typer or typed-argument-parser to parse args
3
3
  """
4
4
 
5
- from machineconfig.scripts.python.helpers_cloud.helpers2 import parse_cloud_source_target
6
- from machineconfig.scripts.python.helpers_cloud.cloud_helpers import Args
7
- from machineconfig.scripts.python.helpers_cloud.cloud_mount import get_mprocs_mount_txt
8
5
 
9
6
  from typing import Annotated, Optional
10
7
  import typer
11
- from rich.console import Console
12
- from rich.panel import Panel
13
-
14
- console = Console()
15
8
 
16
9
 
17
10
  def main(
@@ -27,6 +20,14 @@ def main(
27
20
  delete: Annotated[bool, typer.Option("--delete", "-D", help="Delete files in remote that are not in local.")] = False,
28
21
  verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Verbosity of mprocs to show details of syncing.")] = False,
29
22
  ) -> None:
23
+
24
+ from machineconfig.scripts.python.helpers_cloud.helpers2 import parse_cloud_source_target
25
+ from machineconfig.scripts.python.helpers_cloud.cloud_helpers import Args
26
+ from machineconfig.scripts.python.helpers_cloud.cloud_mount import get_mprocs_mount_txt
27
+ from rich.console import Console
28
+ from rich.panel import Panel
29
+ console = Console()
30
+
30
31
  title = "☁️ Cloud Sync Utility"
31
32
  console.print(Panel(title, title_align="left", border_style="blue"))
32
33
 
@@ -20,9 +20,9 @@ def get_read_data_pycode(path: str):
20
20
  from rich.panel import Panel
21
21
  from rich.text import Text
22
22
  from rich.console import Console
23
- from machineconfig.utils.path_extended import PathExtended
23
+ from pathlib import Path
24
24
  console = Console()
25
- p = PathExtended(path).absolute()
25
+ p = Path(path).absolute()
26
26
  try:
27
27
  from machineconfig.utils.files.read import Read
28
28
  from machineconfig.utils.accessories import pprint
@@ -95,13 +95,12 @@ def main(
95
95
  if md_file.name != "slides.md":
96
96
  SLIDEV_REPO.joinpath(md_file.name).with_name(name="slides.md", inplace=True, overwrite=True)
97
97
 
98
- import socket
99
-
100
- try:
101
- local_ip_v4 = socket.gethostbyname(socket.gethostname() + ".local")
102
- except Exception:
103
- print("⚠️ Warning: Could not get local_ip_v4. This might be due to running in a WSL instance.")
104
- local_ip_v4 = socket.gethostbyname(socket.gethostname())
98
+ import machineconfig.scripts.python.nw.address as helper
99
+ res = helper.select_lan_ipv4(prefer_vpn=False)
100
+ if res is None:
101
+ print("❌ Error: Could not determine local LAN IPv4 address for presentation.")
102
+ raise typer.Exit(code=1)
103
+ local_ip_v4 = res
105
104
 
106
105
  print("🌐 Presentation will be served at:")
107
106
  print(f" - http://{platform.node()}:{port}")
@@ -45,18 +45,17 @@ def main(
45
45
  dest_path = Path(destination).expanduser().absolute()
46
46
  dest_path.mkdir(parents=True, exist_ok=True)
47
47
  new_path = dest_path.joinpath(orig_path.name)
48
- from machineconfig.utils.path_extended import PathExtended
49
48
  match method:
50
49
  case "copy" | "c":
51
50
  try:
52
- copy_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=ON_CONFLICT_MAPPER[on_conflict])
51
+ copy_map(config_file_default_path=orig_path, self_managed_config_file_path=new_path, on_conflict=ON_CONFLICT_MAPPER[on_conflict]) # type: ignore[arg-type]
53
52
  except Exception as e:
54
53
  typer.echo(f"[red]Error:[/] {e}")
55
54
  typer.Exit(code=1)
56
55
  return
57
56
  case "symlink" | "s":
58
57
  try:
59
- symlink_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=ON_CONFLICT_MAPPER[on_conflict])
58
+ symlink_map(config_file_default_path=orig_path, self_managed_config_file_path=new_path, on_conflict=ON_CONFLICT_MAPPER[on_conflict]) # type: ignore[arg-type]
60
59
  except Exception as e:
61
60
  typer.echo(f"[red]Error:[/] {e}")
62
61
  typer.Exit(code=1)
@@ -66,10 +65,10 @@ def main(
66
65
 
67
66
  # mapper_snippet = "\n".join(
68
67
  # [
69
- # f"[bold]📝 Edit configuration file:[/] [cyan]nano {PathExtended(CONFIG_ROOT)}/symlinks/mapper.toml[/cyan]",
68
+ # f"[bold]📝 Edit configuration file:[/] [cyan]nano {Path(CONFIG_ROOT)}/symlinks/mapper.toml[/cyan]",
70
69
  # "",
71
70
  # f"[{new_path.parent.name}]",
72
- # f"{orig_path.name.split('.')[0]} = {{ this = '{orig_path.collapseuser().as_posix()}', to_this = '{new_path.collapseuser().as_posix()}' }}",
71
+ # f"{orig_path.name.split('.')[0]} = {{ this = '{orig_path.as_posix()}', to_this = '{new_path.as_posix()}' }}",
73
72
  # ]
74
73
  # )
75
74
  # console.print(
@@ -35,12 +35,63 @@ def add_ssh_identity():
35
35
 
36
36
 
37
37
  def show_address():
38
- import socket
39
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
40
- s.connect(('8.8.8.8',80))
41
- local_ip_v4 = s.getsockname()[0]
42
- s.close()
43
- print(f"This computer is @ {local_ip_v4}")
38
+ """📌 Show this computer addresses on network"""
39
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
40
+ from pathlib import Path
41
+ from machineconfig.utils.accessories import randstr
42
+ tmp_file = Path.home().joinpath("tmp_results/tmp_files/ipinfo_result_" + randstr(8) + ".json")
43
+ tmp_file.parent.mkdir(parents=True, exist_ok=True)
44
+ install_if_missing("ipinfo")
45
+ script = f"""
46
+ ipinfo myip --json > "{tmp_file.as_posix()}"
47
+ """
48
+ from machineconfig.utils.code import run_shell_script
49
+ run_shell_script(script=script)
50
+ import json
51
+ loaded_json = json.loads(tmp_file.read_text(encoding="utf-8"))
52
+ from rich import print_json
53
+ print_json(data=loaded_json)
54
+
55
+ import machineconfig.scripts.python.nw.address as helper
56
+ from rich.table import Table
57
+ from rich.console import Console
58
+ res = helper.get_all_ipv4_addresses()
59
+ res.append( ("Public IP", loaded_json.get("ip", "N/A")))
60
+
61
+ # loc = loaded_json["loc"]
62
+ # cmd = f"""curl "https://maps.geoapify.com/v1/staticmap?style=osm-bright&width=600&height=300&center=lonlat:{loc}&zoom=6&marker=lonlat:{loc};color:%23ff0000;size:medium&apiKey=$GEOAPIFY_API_KEY" -o map.png && chafa map.png"""
63
+ # from machineconfig.utils.code import run_shell_script
64
+ # run_shell_script(script=cmd)
65
+
66
+ table = Table(title="Network Interfaces")
67
+ table.add_column("Interface", style="cyan")
68
+ table.add_column("IP Address", style="green")
69
+
70
+ for iface, ip in res:
71
+ table.add_row(iface, ip)
72
+
73
+ console = Console()
74
+ console.print(table)
75
+
76
+ res = helper.select_lan_ipv4(prefer_vpn=False)
77
+ if res is not None:
78
+ # ip, iface = res
79
+ # print(f"Selected IP: {ip} on interface: {iface}")
80
+ print(f"LAN IPv4: {res}")
81
+ else:
82
+ print("No network interfaces found.")
83
+
84
+
85
+
86
+ def bind_wsl_port(port: Annotated[int, typer.Option(..., "--port", "-p", help="Port number to bind")]):
87
+ code = f"""
88
+
89
+ $wsl_ip = (wsl.exe hostname -I).Trim().Split(' ')[0]
90
+ netsh interface portproxy add v4tov4 listenport={port} listenaddress=0.0.0.0 connectport={port} connectaddress=$wsl_ip
91
+
92
+ """
93
+ from machineconfig.utils.code import exit_then_run_shell_script
94
+ exit_then_run_shell_script(code)
44
95
 
45
96
 
46
97
  def debug_ssh():
@@ -101,6 +152,28 @@ def wifi_select(
101
152
  console.print("[blue]👋 Goodbye![/blue]")
102
153
 
103
154
 
155
+
156
+ def reset_cloudflare_tunnel():
157
+ code = """
158
+ # cloudflared tunnel route dns glenn # creates CNAMES in Cloudflare dashboard
159
+ # sudo systemctl stop cloudflared
160
+ # test: cloudflared tunnel run glenn
161
+ home_dir=$HOME
162
+ cloudflared_path="$home_dir/.local/bin/cloudflared"
163
+ sudo $cloudflared_path service uninstall
164
+ sudo rm /etc/cloudflared/config.yml || true
165
+ sudo $cloudflared_path --config $home_dir/.cloudflared/config.yml service install
166
+ """
167
+ print(code)
168
+ def add_ip_exclusion_to_warp(ip: Annotated[str, typer.Option(..., "--ip", help="IP address to exclude from WARP")]):
169
+ code = f"""
170
+ sudo warp-cli tunnel ip add {ip}
171
+ sudo warp-cli disconnect
172
+ sudo warp-cli connect
173
+ """
174
+ print(code)
175
+
176
+
104
177
  def get_app():
105
178
  nw_apps = typer.Typer(help="🔐 [n] Network subcommands", no_args_is_help=True, add_help_option=False, add_completion=False)
106
179
  nw_apps.command(name="share-terminal", help="📡 [t] Share terminal via web browser")(cli_terminal.main)
@@ -128,7 +201,15 @@ def get_app():
128
201
  nw_apps.command(name="debug-ssh", help="🐛 [d] Debug SSH connection")(debug_ssh)
129
202
  nw_apps.command(name="d", help="Debug SSH connection", hidden=True)(debug_ssh)
130
203
 
131
- nw_apps.command(name="wifi-select", no_args_is_help=True, help="📶 WiFi connection utility.")(wifi_select)
204
+ nw_apps.command(name="wifi-select", no_args_is_help=True, help="📶 [w] WiFi connection utility.")(wifi_select)
132
205
  nw_apps.command(name="w", no_args_is_help=True, hidden=True)(wifi_select)
133
206
 
207
+ nw_apps.command(name="bind-wsl-port", help="🔌 [b] Bind WSL port to Windows host", no_args_is_help=True)(bind_wsl_port)
208
+ nw_apps.command(name="b", help="Bind WSL port to Windows host", hidden=True, no_args_is_help=True)(bind_wsl_port)
209
+
210
+ nw_apps.command(name="reset-cloudflare-tunnel", help="☁️ [r] Reset Cloudflare tunnel service")(reset_cloudflare_tunnel)
211
+ nw_apps.command(name="r", help="Reset Cloudflare tunnel service", hidden=True)(reset_cloudflare_tunnel)
212
+ nw_apps.command(name="add-ip-exclusion-to-warp", help="🚫 [p] Add IP exclusion to WARP")(add_ip_exclusion_to_warp)
213
+ nw_apps.command(name="p", help="Add IP exclusion to WARP", hidden=True)(add_ip_exclusion_to_warp)
214
+
134
215
  return nw_apps
@@ -30,7 +30,8 @@ uv tool install --upgrade machineconfig
30
30
  if platform.system() == "Windows":
31
31
  from machineconfig.utils.code import exit_then_run_shell_script, get_uv_command_executing_python_script
32
32
  from machineconfig.utils.meta import lambda_to_python_script
33
- python_script = lambda_to_python_script(lambda: copy_both_assets(), in_global=True, import_module=False)
33
+ python_script = lambda_to_python_script(lambda: copy_both_assets(),
34
+ in_global=True, import_module=False)
34
35
  uv_command, _py_file = get_uv_command_executing_python_script(python_script=python_script, uv_with=["machineconfig"], uv_project_dir=None)
35
36
  exit_then_run_shell_script(shell_script + "\n" + uv_command, strict=True)
36
37
  else:
@@ -52,9 +53,9 @@ def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-n
52
53
  else:
53
54
  import platform
54
55
  if platform.system() == "Windows":
55
- run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.57" """)
56
+ run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.79" """)
56
57
  else:
57
- run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.57" """)
58
+ run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.79" """)
58
59
  from machineconfig.profile.create_shell_profile import create_default_shell_profile
59
60
  if not no_copy_assets:
60
61
  create_default_shell_profile() # involves copying assets too
@@ -77,10 +78,10 @@ def navigate():
77
78
  import machineconfig.scripts.python as navigator
78
79
  from pathlib import Path
79
80
  path = Path(navigator.__file__).resolve().parent.joinpath("devops_navigator.py")
80
- from machineconfig.utils.code import run_shell_script
81
+ from machineconfig.utils.code import exit_then_run_shell_script
81
82
  if Path.home().joinpath("code/machineconfig").exists(): executable = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with textual"""
82
- else: executable = """--with "machineconfig>=7.57,textual" """
83
- run_shell_script(f"""uv run {executable} {path}""")
83
+ else: executable = """--with "machineconfig>=7.79,textual" """
84
+ exit_then_run_shell_script(f"""uv run {executable} {path}""")
84
85
 
85
86
 
86
87
  def run_python(ip: Annotated[str, typer.Argument(..., help="Python command to run in the machineconfig environment")],
@@ -1,6 +1,5 @@
1
+
1
2
  import typer
2
- # import platform
3
- # import sys
4
3
  from typing import Annotated
5
4
 
6
5
 
@@ -12,7 +11,7 @@ Usage examples:
12
11
  devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle
13
12
  devops network receive -- croc --relay 10.17.62.206:443 7121-donor-olympic-bicycle
14
13
  """
15
- from machineconfig.utils.installer_utils.installer import install_if_missing
14
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
16
15
  install_if_missing(which="croc")
17
16
  import platform
18
17
  import sys
@@ -100,15 +99,16 @@ def share_file_send(path: Annotated[str, typer.Argument(help="Path to the file o
100
99
  qrcode: Annotated[bool, typer.Option("--qrcode", "--qr", help="Show receive code as a qrcode")] = False,
101
100
  ) -> None:
102
101
  """Send a file using croc with relay server."""
103
- from machineconfig.utils.installer_utils.installer import install_if_missing
102
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
104
103
  install_if_missing(which="croc")
105
104
  # Get relay server IP from environment or use default
106
- import socket
105
+ import machineconfig.scripts.python.nw.address as helper
106
+ res = helper.select_lan_ipv4(prefer_vpn=False)
107
+ if res is None:
108
+ typer.echo("❌ Error: Could not determine local LAN IPv4 address for relay.", err=True)
109
+ raise typer.Exit(code=1)
110
+ local_ip_v4 = res
107
111
  import platform
108
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
109
- s.connect(('8.8.8.8',80))
110
- local_ip_v4 = s.getsockname()[0]
111
- s.close()
112
112
  relay_port = "443"
113
113
  is_windows = platform.system() == "Windows"
114
114
 
@@ -38,18 +38,16 @@ def web_file_explorer(
38
38
  username: Annotated[Optional[str], typer.Option("--username", "-u", help="Username for share access (default: current user)")] = None,
39
39
  password: Annotated[Optional[str], typer.Option("--password", "-w", help="Password for share access (default: from ~/dotfiles/creds/passwords/quick_password)")] = None,
40
40
  over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the share server over the internet using ngrok")] = False,
41
- backend: Annotated[str, typer.Option("--backend", "-b", help="Backend to use: filebrowser (default), miniserve, or easy-sharing")] = "filebrowser"
41
+ backend: Annotated[str, typer.Option("--backend", "-b", help="Backend to use: filebrowser (default), miniserve, qrcp, or easy-sharing")] = "miniserve",
42
42
  ) -> None:
43
- from machineconfig.utils.installer_utils.installer import install_if_missing
44
-
45
- if backend not in ["filebrowser", "miniserve", "easy-sharing"]:
46
- typer.echo(f"❌ ERROR: Invalid backend '{backend}'. Must be one of: filebrowser, miniserve, easy-sharing", err=True)
43
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
44
+
45
+ if backend not in ["filebrowser", "miniserve", "qrcp", "easy-sharing"]:
46
+ typer.echo(f"❌ ERROR: Invalid backend '{backend}'. Must be one of: filebrowser, miniserve, qrcp, easy-sharing", err=True)
47
47
  raise typer.Exit(code=1)
48
-
49
48
  install_if_missing(which=backend)
50
49
  if over_internet:
51
50
  install_if_missing(which="ngrok")
52
-
53
51
  if username is None:
54
52
  import getpass
55
53
  username = getpass.getuser()
@@ -65,11 +63,12 @@ def web_file_explorer(
65
63
  if port is None:
66
64
  port = 8080
67
65
 
68
- import socket
69
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
70
- s.connect(('8.8.8.8', 80))
71
- local_ip_v4 = s.getsockname()[0]
72
- s.close()
66
+ import machineconfig.scripts.python.nw.address as helper
67
+ res = helper.select_lan_ipv4(prefer_vpn=False)
68
+ if res is None:
69
+ typer.echo("❌ ERROR: Could not determine local LAN IPv4 address for share server.", err=True)
70
+ raise typer.Exit(code=1)
71
+ local_ip_v4 = res
73
72
 
74
73
  protocol = "http"
75
74
  display_share_url(local_ip_v4, port, protocol)
@@ -90,6 +89,8 @@ filebrowser --address 0.0.0.0 --port {port} --root "{path_obj}" --database {db_p
90
89
  command = f"""miniserve --port {port} --interfaces 0.0.0.0 --auth {username}:{password} --upload-files --mkdir --enable-tar --enable-tar-gz --enable-zip --qrcode "{path_obj}" """
91
90
  elif backend == "easy-sharing":
92
91
  command = f"""easy-sharing --port {port} --username {username} --password "{password}" "{path_obj}" """
92
+ elif backend == "qrcp":
93
+ command = f"""qrcp "{path_obj}" """
93
94
  else:
94
95
  typer.echo(f"❌ ERROR: Unknown backend '{backend}'", err=True)
95
96
  raise typer.Exit(code=1)
@@ -60,7 +60,7 @@ def main(
60
60
  ssl_ca: Annotated[Optional[str], typer.Option("--ssl-ca", "-A", help="SSL CA file path for client certificate verification")] = None,
61
61
  over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the terminal over the internet using ngrok")] = False
62
62
  ) -> None:
63
- from machineconfig.utils.installer_utils.installer import install_if_missing
63
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
64
64
  install_if_missing("ttyd")
65
65
  if over_internet: install_if_missing("ngrok")
66
66
 
@@ -96,11 +96,12 @@ def main(
96
96
  if ssl_ca and not Path(ssl_ca).exists():
97
97
  raise FileNotFoundError(f"SSL CA file not found: {ssl_ca}")
98
98
 
99
- import socket
100
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
101
- s.connect(('8.8.8.8',80))
102
- local_ip_v4 = s.getsockname()[0]
103
- s.close()
99
+ import machineconfig.scripts.python.nw.address as helper
100
+ res = helper.select_lan_ipv4(prefer_vpn=False)
101
+ if res is None:
102
+ print("❌ Error: Could not determine local LAN IPv4 address for terminal.")
103
+ raise typer.Exit(code=1)
104
+ local_ip_v4 = res
104
105
 
105
106
  # Display the flashy terminal announcement
106
107
  protocol = "https" if ssl else "http"
@@ -2,78 +2,6 @@
2
2
 
3
3
  import typer
4
4
  from typing import Annotated, Optional
5
- from pathlib import Path
6
-
7
- def download(
8
- url: Annotated[Optional[str], typer.Argument(..., help="The URL to download the file from.")] = None,
9
- decompress: Annotated[bool, typer.Option("--decompress", "-d", help="Decompress the file if it's an archive.")] = False,
10
- output: Annotated[Optional[str], typer.Option("--output", "-o", help="The output file path.")] = None,
11
- ) -> None:
12
- if url is None:
13
- typer.echo("❌ Error: URL is required.", err=True)
14
- raise typer.Exit(code=1)
15
- typer.echo(f"📥 Downloading from: {url}")
16
- download_path = Path(output) if output else Path(url.split("/")[-1])
17
- import requests
18
- import subprocess
19
- try:
20
- response = requests.get(url, allow_redirects=True, stream=True, timeout=60)
21
- response.raise_for_status()
22
-
23
- total_size = int(response.headers.get('content-length', 0))
24
-
25
- with open(download_path, 'wb') as f:
26
- if total_size == 0:
27
- f.write(response.content)
28
- else:
29
- downloaded = 0
30
- chunk_size = 8192
31
- for chunk in response.iter_content(chunk_size=chunk_size):
32
- if chunk:
33
- f.write(chunk)
34
- downloaded += len(chunk)
35
- progress = (downloaded / total_size) * 100
36
- typer.echo(f"\r⏬ Progress: {progress:.1f}% ({downloaded}/{total_size} bytes)", nl=False)
37
- typer.echo()
38
-
39
- typer.echo(f"✅ Downloaded to: {download_path}")
40
- except requests.exceptions.RequestException as e:
41
- typer.echo(f"❌ Download failed: {e}", err=True)
42
- raise typer.Exit(code=1)
43
- except OSError as e:
44
- typer.echo(f"❌ File write error: {e}", err=True)
45
- raise typer.Exit(code=1)
46
-
47
- if decompress:
48
- typer.echo(f"📦 Decompressing: {download_path}")
49
-
50
- base_name = download_path.name
51
- parts = base_name.split('.')
52
- base_name = parts[0] if parts else download_path.stem
53
-
54
- extract_dir = download_path.parent / base_name
55
- extract_dir.mkdir(parents=True, exist_ok=True)
56
-
57
- try:
58
- subprocess.run(
59
- ["ouch", "decompress", str(download_path), "--dir", str(extract_dir)],
60
- check=True,
61
- capture_output=True,
62
- text=True
63
- )
64
- typer.echo(f"✅ Decompressed to: {extract_dir}")
65
-
66
- if download_path.exists():
67
- download_path.unlink()
68
- typer.echo(f"🗑️ Removed archive: {download_path}")
69
-
70
- except subprocess.CalledProcessError as e:
71
- typer.echo(f"❌ Decompression failed: {e.stderr}", err=True)
72
- raise typer.Exit(code=1)
73
- except FileNotFoundError:
74
- typer.echo("❌ Error: ouch command not found. Please install ouch.", err=True)
75
- typer.echo("💡 Install with: cargo install ouch", err=True)
76
- raise typer.Exit(code=1)
77
5
 
78
6
 
79
7
  def merge_pdfs(
@@ -108,7 +36,8 @@ def merge_pdfs(
108
36
  writer.write(output_path)
109
37
  print(f"✅ Merged PDF saved to: {output_path}")
110
38
  from machineconfig.utils.meta import lambda_to_python_script
111
- code = lambda_to_python_script(lambda : merge_pdfs_internal(pdfs=pdfs, output=output, compress=compress), in_global=True, import_module=False)
39
+ code = lambda_to_python_script(lambda : merge_pdfs_internal(pdfs=pdfs, output=output, compress=compress),
40
+ in_global=True, import_module=False)
112
41
  from machineconfig.utils.code import run_shell_script, get_uv_command_executing_python_script
113
42
  uv_command, _py_file = get_uv_command_executing_python_script(python_script=code, uv_with=["pypdf"], uv_project_dir=None)
114
43
  run_shell_script(uv_command)
@@ -2,7 +2,6 @@
2
2
 
3
3
  # import subprocess
4
4
  from machineconfig.utils.io import read_ini
5
- from machineconfig.utils.path_extended import PathExtended
6
5
  from machineconfig.utils.source_of_truth import LIBRARY_ROOT, DEFAULTS_PATH
7
6
  from machineconfig.utils.code import print_code
8
7
  from machineconfig.utils.options import choose_cloud_interactively, choose_from_options
@@ -11,6 +10,7 @@ from platform import system
11
10
  from typing import Any, Literal, Optional
12
11
  from rich.console import Console
13
12
  from rich.panel import Panel
13
+ from pathlib import Path
14
14
  import tomllib
15
15
 
16
16
 
@@ -56,13 +56,13 @@ def main_backup_retrieve(direction: OPTIONS, which: Optional[str], cloud: Option
56
56
  flags += "e" if item["encrypt"] == "True" else ""
57
57
  flags += "r" if item["rel2home"] == "True" else ""
58
58
  flags += "o" if system().lower() in item_name else ""
59
- console.print(Panel(f"📦 PROCESSING: {item_name}\n📂 Path: {PathExtended(item['path']).as_posix()}\n🏳️ Flags: {flags or 'None'}", title=f"[bold blue]Processing Item: {item_name}[/bold blue]", border_style="blue"))
59
+ console.print(Panel(f"📦 PROCESSING: {item_name}\n📂 Path: {Path(item['path']).as_posix()}\n🏳️ Flags: {flags or 'None'}", title=f"[bold blue]Processing Item: {item_name}[/bold blue]", border_style="blue"))
60
60
  if flags:
61
61
  flags = "-" + flags
62
62
  if direction == "BACKUP":
63
- program += f"""\ncloud_copy "{PathExtended(item["path"]).as_posix()}" $cloud {flags}\n"""
63
+ program += f"""\ncloud_copy "{Path(item["path"]).as_posix()}" $cloud {flags}\n"""
64
64
  elif direction == "RETRIEVE":
65
- program += f"""\ncloud_copy $cloud "{PathExtended(item["path"]).as_posix()}" {flags}\n"""
65
+ program += f"""\ncloud_copy $cloud "{Path(item["path"]).as_posix()}" {flags}\n"""
66
66
  else:
67
67
  console.print(Panel('❌ ERROR: INVALID DIRECTION\n⚠️ Direction must be either "BACKUP" or "RETRIEVE"', title="[bold red]Error: Invalid Direction[/bold red]", border_style="red"))
68
68
  raise RuntimeError(f"Unknown direction: {direction}")
@@ -17,22 +17,6 @@ from machineconfig.utils.source_of_truth import CONFIG_ROOT, DEFAULTS_PATH, LIBR
17
17
  console = Console()
18
18
 
19
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
20
 
37
21
  def _check_shell_profile_status() -> dict[str, Any]:
38
22
  """Check shell profile configuration status."""
@@ -480,10 +464,14 @@ def main() -> None:
480
464
  console.print("\n")
481
465
  console.print(Panel(Text("📊 Machine Status Report", justify="center", style="bold white"), style="bold blue", padding=(1, 2)))
482
466
  console.print("\n")
483
-
484
- system_info = _check_system_info()
467
+
468
+ # system_info = _check_system_info()
469
+ # from machineconfig.scripts.python.helpers_devops.devops_system_info import _check_system_info
470
+ from machineconfig.scripts.python.helpers_utils.path import get_machine_specs
471
+ system_info = get_machine_specs()
472
+ from typing import cast
473
+ system_info = cast(dict[str, str], system_info)
485
474
  _display_system_info(system_info)
486
-
487
475
  shell_status = _check_shell_profile_status()
488
476
  _display_shell_status(shell_status)
489
477
 
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
  import os
3
- from machineconfig.utils.path_extended import PathExtended
3
+ from pathlib import Path
4
4
  import platform
5
5
 
6
6
 
@@ -12,7 +12,7 @@ def parse_pyfile(file_path: str):
12
12
  func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
13
13
  import ast
14
14
 
15
- parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding="utf-8"))
15
+ parsed_ast = ast.parse(Path(file_path).read_text(encoding="utf-8"))
16
16
  functions = [node for node in ast.walk(parsed_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
17
17
  module__doc__ = ast.get_docstring(parsed_ast)
18
18
  main_option = f"RUN AS MAIN -- {module__doc__ if module__doc__ is not None else 'NoDocs'}"
@@ -102,7 +102,6 @@ def wrap_import_in_try_except(import_line: str, pyfile: str, repo_root: Optional
102
102
  print(fr"❌ Failed to import `{pyfile}` as a module: {ex} ")
103
103
  print("⚠️ Attempting import with ad-hoc `$PATH` manipulation. DO NOT pickle any objects in this session as correct deserialization cannot be guaranteed.")
104
104
  import sys
105
- from pathlib import Path
106
105
  sys.path.append(str(Path(pyfile).parent))
107
106
  if repo_root is not None:
108
107
  sys.path.append(repo_root)
@@ -5,11 +5,10 @@ from typing import Optional
5
5
  import tomllib
6
6
  from pathlib import Path
7
7
  from machineconfig.utils.accessories import randstr
8
- from machineconfig.utils.path_extended import PathExtended
9
8
  from machineconfig.utils.options import choose_from_options
10
9
 
11
10
 
12
- def choose_function_or_lines(choice_file: PathExtended, kwargs_dict: dict[str, object]) -> tuple[Optional[str], PathExtended, dict[str, object]]:
11
+ def choose_function_or_lines(choice_file: Path, kwargs_dict: dict[str, object]) -> tuple[Optional[str], Path, dict[str, object]]:
13
12
  """
14
13
  Choose a function to run from a Python file or lines from a shell script.
15
14
 
@@ -46,7 +45,7 @@ def choose_function_or_lines(choice_file: PathExtended, kwargs_dict: dict[str, o
46
45
  continue
47
46
  options.append(line)
48
47
  chosen_lines = choose_from_options(msg="Choose a line to run", options=options, fzf=True, multi=True)
49
- choice_file = PathExtended.tmpfile(suffix=".sh")
48
+ choice_file = Path.home().joinpath(f"tmp_results/tmp_scripts/shell/{randstr(10)}.sh")
50
49
  choice_file.parent.mkdir(parents=True, exist_ok=True)
51
50
  choice_file.write_text("\n".join(chosen_lines), encoding="utf-8")
52
51
  choice_function = None
@@ -55,15 +54,13 @@ def choose_function_or_lines(choice_file: PathExtended, kwargs_dict: dict[str, o
55
54
 
56
55
 
57
56
  def get_command_streamlit(choice_file: Path, environment: str, repo_root: Optional[Path]) -> str:
58
- import socket
59
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
60
- try:
61
- s.connect(("8.8.8.8", 1))
62
- local_ip_v4 = s.getsockname()[0]
63
- except Exception:
64
- local_ip_v4 = socket.gethostbyname(socket.gethostname())
65
- finally:
66
- s.close()
57
+ # from machineconfig.scripts.python.helpers_utils.path import get_machine_specs
58
+ from machineconfig.scripts.python.nw.address import select_lan_ipv4
59
+ res = select_lan_ipv4(prefer_vpn=False)
60
+ if res is None:
61
+ raise RuntimeError("Could not determine local LAN IPv4 address for streamlit app.")
62
+ local_ip_v4 = res
63
+
67
64
  computer_name = platform.node()
68
65
  port = 8501
69
66
  toml_path: Optional[Path] = None
@@ -82,7 +79,7 @@ def get_command_streamlit(choice_file: Path, environment: str, repo_root: Option
82
79
  port = config["server"]["port"]
83
80
  secrets_path = toml_path.with_name("secrets.toml")
84
81
  if repo_root is not None:
85
- secrets_template_path = Path.home().joinpath(f"dotfiles/creds/streamlit/{PathExtended(repo_root).name}/{choice_file.name}/secrets.toml")
82
+ secrets_template_path = Path.home().joinpath(f"dotfiles/creds/streamlit/{Path(repo_root).name}/{choice_file.name}/secrets.toml")
86
83
  if environment != "" and not secrets_path.exists() and secrets_template_path.exists():
87
84
  secrets_template = tomllib.loads(secrets_template_path.read_text(encoding="utf-8"))
88
85
  if environment == "ip":
@@ -101,6 +98,19 @@ def get_command_streamlit(choice_file: Path, environment: str, repo_root: Option
101
98
  except Exception as ex:
102
99
  print(ex)
103
100
  raise ex
101
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
102
+ install_if_missing("qrterminal")
103
+ script = f"""
104
+ qrterminal "http://{local_ip_v4}:{port}"
105
+ echo "http://{local_ip_v4}:{port}"
106
+ qrterminal "http://{computer_name}:{port}"
107
+ echo "http://{computer_name}:{port}"
108
+ """
109
+ # from machineconfig.utils.code import run_shell_script
110
+ # run_shell_script(script)
111
+ from machineconfig.utils.code import print_code
112
+ print_code(code=script, lexer="shell", desc="Streamlit QR Codes and URLs")
113
+
104
114
  message = f"🚀 Streamlit app is running @:\n1- http://{local_ip_v4}:{port}\n2- http://{computer_name}:{port}\n3- http://localhost:{port}"
105
115
  from rich.panel import Panel
106
116
  from rich import print as rprint