machineconfig 7.49__py3-none-any.whl → 7.64__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 (93) hide show
  1. machineconfig/cluster/sessions_managers/utils/maker.py +21 -11
  2. machineconfig/jobs/installer/custom/boxes.py +2 -2
  3. machineconfig/jobs/installer/custom/hx.py +16 -12
  4. machineconfig/jobs/installer/custom_dev/brave.py +1 -1
  5. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  6. machineconfig/jobs/installer/custom_dev/code.py +4 -1
  7. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
  8. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -10
  9. machineconfig/jobs/installer/custom_dev/sysabc.py +119 -0
  10. machineconfig/jobs/installer/custom_dev/wezterm.py +2 -19
  11. machineconfig/jobs/installer/installer_data.json +739 -25
  12. machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
  13. machineconfig/jobs/installer/package_groups.py +49 -83
  14. machineconfig/logger.py +0 -1
  15. machineconfig/profile/create_links_export.py +21 -7
  16. machineconfig/profile/mapper.toml +1 -4
  17. machineconfig/scripts/linux/wrap_mcfg +1 -1
  18. machineconfig/scripts/python/croshell.py +20 -43
  19. machineconfig/scripts/python/devops.py +3 -4
  20. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  21. machineconfig/scripts/python/fire_jobs.py +53 -39
  22. machineconfig/scripts/python/ftpx.py +4 -2
  23. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +0 -12
  24. machineconfig/scripts/python/helpers_croshell/crosh.py +3 -3
  25. machineconfig/scripts/python/helpers_devops/cli_config.py +3 -19
  26. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
  27. machineconfig/scripts/python/helpers_devops/cli_self.py +12 -6
  28. machineconfig/scripts/python/helpers_devops/cli_share_file.py +2 -2
  29. machineconfig/scripts/python/helpers_devops/cli_share_server.py +1 -1
  30. machineconfig/scripts/python/helpers_devops/cli_terminal.py +1 -1
  31. machineconfig/scripts/python/helpers_devops/cli_utils.py +1 -152
  32. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  33. machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -20
  34. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
  35. machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +1 -1
  36. machineconfig/scripts/python/helpers_repos/clone.py +0 -1
  37. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +9 -3
  38. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  39. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  40. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  41. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +7 -7
  42. machineconfig/scripts/python/helpers_utils/download.py +151 -0
  43. machineconfig/scripts/python/helpers_utils/path.py +106 -0
  44. machineconfig/scripts/python/interactive.py +17 -26
  45. machineconfig/scripts/python/nw/devops_add_ssh_key.py +21 -5
  46. machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
  47. machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
  48. machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
  49. machineconfig/scripts/python/sessions.py +37 -22
  50. machineconfig/scripts/python/utils.py +8 -3
  51. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  52. machineconfig/settings/shells/nushell/init.nu +2 -2
  53. machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
  54. machineconfig/settings/shells/zsh/init.sh +1 -8
  55. machineconfig/settings/yazi/init.lua +45 -24
  56. machineconfig/settings/yazi/keymap_windows.toml +1 -2
  57. machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
  58. machineconfig/setup_linux/__init__.py +0 -1
  59. machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -10
  60. machineconfig/setup_mac/__init__.py +2 -3
  61. machineconfig/setup_windows/__init__.py +0 -3
  62. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -10
  63. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +16 -0
  64. machineconfig/utils/code.py +2 -2
  65. machineconfig/utils/files/headers.py +2 -2
  66. machineconfig/utils/installer_utils/installer_class.py +42 -40
  67. machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +61 -101
  68. machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -68
  69. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +11 -51
  70. machineconfig/utils/io.py +0 -1
  71. machineconfig/utils/meta.py +29 -15
  72. machineconfig/utils/options.py +1 -1
  73. machineconfig/utils/path_extended.py +40 -19
  74. machineconfig/utils/path_helper.py +75 -21
  75. machineconfig/utils/schemas/layouts/layout_types.py +1 -1
  76. machineconfig/utils/ssh.py +3 -3
  77. machineconfig-7.64.dist-info/METADATA +124 -0
  78. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/RECORD +84 -87
  79. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  80. machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
  81. machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
  82. machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
  83. machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
  84. machineconfig/setup_linux/apps.sh +0 -66
  85. machineconfig/setup_mac/apps.sh +0 -73
  86. machineconfig/setup_windows/apps.ps1 +0 -62
  87. machineconfig-7.49.dist-info/METADATA +0 -92
  88. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  89. /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
  90. /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
  91. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/WHEEL +0 -0
  92. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/entry_points.txt +0 -0
  93. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/top_level.txt +0 -0
@@ -8,11 +8,9 @@ fire
8
8
  """
9
9
 
10
10
  from machineconfig.utils.ve import get_ve_path_and_ipython_profile
11
- from machineconfig.utils.options import choose_from_options
12
- from machineconfig.utils.path_helper import match_file_name, sanitize_path
13
- from machineconfig.utils.path_extended import PathExtended
14
11
  from machineconfig.utils.accessories import get_repo_root, randstr
15
12
  from machineconfig.scripts.python.helpers_fire_command.fire_jobs_args_helper import FireJobArgs, extract_kwargs, parse_fire_args_from_context
13
+ from machineconfig.utils.path_helper import get_choice_file
16
14
 
17
15
  import platform
18
16
  from typing import Optional, Annotated
@@ -21,25 +19,12 @@ import typer
21
19
 
22
20
 
23
21
  def route(args: FireJobArgs, fire_args: str = "") -> None:
24
- path_obj = sanitize_path(args.path)
25
- if not path_obj.exists():
26
- suffixes = {".py", ".sh", ".ps1"}
27
- choice_file = match_file_name(sub_string=args.path, search_root=PathExtended.cwd(), suffixes=suffixes)
28
- elif path_obj.is_dir():
29
- from machineconfig.scripts.python.helpers_fire_command.file_wrangler import search_for_files_of_interest
30
- print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
31
- files = search_for_files_of_interest(path_obj)
32
- print(f"🔍 Got #{len(files)} results.")
33
- choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
34
- choice_file = PathExtended(choice_file)
35
- else:
36
- choice_file = path_obj
37
-
22
+ choice_file = get_choice_file(args.path, suffixes=None)
38
23
  repo_root = get_repo_root(Path(choice_file))
39
24
  print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
40
25
  if args.marimo:
41
26
  print(f"🧽 Preparing to launch Marimo notebook for `{choice_file}`...")
42
- tmp_dir = PathExtended.tmp().joinpath(f"tmp_scripts/marimo/{choice_file.stem}_{randstr()}")
27
+ tmp_dir = Path.home().joinpath(f"tmp_results/tmp_scripts/marimo/{choice_file.stem}_{randstr()}")
43
28
  tmp_dir.mkdir(parents=True, exist_ok=True)
44
29
  script = f"""
45
30
  cd {tmp_dir}
@@ -53,7 +38,6 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
53
38
 
54
39
  # ========================= preparing kwargs_dict
55
40
  if choice_file.suffix == ".py":
56
-
57
41
  kwargs_dict = extract_kwargs(args) # This now returns empty dict, but kept for compatibility
58
42
  else:
59
43
  kwargs_dict = {}
@@ -62,17 +46,20 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
62
46
  choice_function: Optional[str] = None # Initialize to avoid unbound variable
63
47
  if args.choose_function:
64
48
  from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import choose_function_or_lines
49
+
65
50
  choice_function, choice_file, kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict)
66
51
  else:
67
52
  choice_function = args.function
68
53
 
69
54
  if choice_file.suffix == ".py":
70
55
  from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import get_command_streamlit
56
+
71
57
  with_project = f"--project {repo_root} " if repo_root is not None else ""
72
58
  if args.streamlit:
73
59
  exe = get_command_streamlit(choice_file=choice_file, environment=args.environment, repo_root=repo_root)
74
60
  exe = f"uv run {with_project} {exe} "
75
- elif args.jupyter: exe = f"uv run {with_project} jupyter-lab"
61
+ elif args.jupyter:
62
+ exe = f"uv run {with_project} jupyter-lab"
76
63
  else:
77
64
  if args.interactive:
78
65
  _ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
@@ -81,20 +68,38 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
81
68
  exe = f"uv run {with_project} ipython -i --no-banner --profile {ipy_profile} "
82
69
  else:
83
70
  exe = f"uv run {with_project} python "
84
- elif choice_file.suffix == ".ps1" or choice_file.suffix == ".sh": exe = "."
85
- elif choice_file.suffix == "": exe = ""
86
- else: raise NotImplementedError(f"File type {choice_file.suffix} not supported, in the sense that I don't know how to fire it.")
71
+ elif choice_file.suffix == ".ps1" or choice_file.suffix == ".sh":
72
+ exe = "."
73
+ elif choice_file.suffix == "":
74
+ exe = ""
75
+ else:
76
+ raise NotImplementedError(f"File type {choice_file.suffix} not supported, in the sense that I don't know how to fire it.")
87
77
 
88
- if args.module or (args.debug and args.choose_function): # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
78
+ if args.module or (args.debug and args.choose_function):
79
+ # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
89
80
  assert choice_file.suffix == ".py", f"File must be a python file to be imported as a module. Got {choice_file}"
90
81
  from machineconfig.scripts.python.helpers_fire_command.file_wrangler import get_import_module_code, wrap_import_in_try_except
91
82
  from machineconfig.utils.meta import lambda_to_python_script
92
83
  from machineconfig.utils.code import print_code
84
+
93
85
  import_code = get_import_module_code(str(choice_file))
94
- import_code_robust = lambda_to_python_script(lambda: wrap_import_in_try_except(import_line=import_code, pyfile=str(choice_file), repo_root=str(repo_root) if repo_root is not None else None), in_global=True, import_module=False)
95
- code_printing = lambda_to_python_script(lambda: print_code(code=import_code_robust, lexer="python", desc="import code"), in_global=True, import_module=False)
96
- if choice_function is not None: calling = f"""res = {choice_function}({("**" + str(kwargs_dict)) if kwargs_dict else ""})"""
97
- else: calling = """# No function selected to call. You can add your code here."""
86
+ import_code_robust = lambda_to_python_script(
87
+ lambda: wrap_import_in_try_except(
88
+ import_line=import_code, pyfile=str(choice_file), repo_root=str(repo_root) if repo_root is not None else None
89
+ ),
90
+ in_global=True,
91
+ import_module=False,
92
+ )
93
+ # print(f"🧩 Preparing import code for module import:\n{import_code}")
94
+ code_printing = lambda_to_python_script(
95
+ lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
96
+ in_global=True, import_module=False
97
+ )
98
+ print(f"🧩 Preparing import code for module import:\n{import_code}")
99
+ if choice_function is not None:
100
+ calling = f"""res = {choice_function}({("**" + str(kwargs_dict)) if kwargs_dict else ""})"""
101
+ else:
102
+ calling = """# No function selected to call. You can add your code here."""
98
103
  choice_file = Path.home().joinpath(f"tmp_results/tmp_scripts/python/{Path(choice_file).parent.name}_{Path(choice_file).stem}_{randstr()}.py")
99
104
  choice_file.parent.mkdir(parents=True, exist_ok=True)
100
105
  choice_file.write_text(import_code_robust + "\n" + code_printing + "\n" + calling, encoding="utf-8")
@@ -117,15 +122,17 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
117
122
  if args.holdDirectory:
118
123
  command = f"{exe} {choice_file}"
119
124
  else:
120
- command = f"cd {choice_file.parent}\n{exe} {choice_file.name}\ncd {PathExtended.cwd()}"
125
+ command = f"cd {choice_file.parent}\n{exe} {choice_file.name}\ncd {Path.cwd()}"
121
126
  elif args.cmd:
122
127
  command = rf""" cd /d {choice_file.parent} & {exe} {choice_file.name} """
123
128
  else:
124
- if choice_file.suffix == "": command = f"{exe} {choice_file} {fire_args}"
125
- else: command = f"{exe} {choice_file} "
126
-
129
+ if choice_file.suffix == "":
130
+ command = f"{exe} {choice_file} {fire_args}"
131
+ else:
132
+ command = f"{exe} {choice_file} "
127
133
 
128
- if not args.cmd: pass
134
+ if not args.cmd:
135
+ pass
129
136
  else:
130
137
  new_line = "\n"
131
138
  command = rf"""start cmd -Argument "/k {command.replace(new_line, " & ")} " """ # this works from powershell
@@ -134,23 +141,27 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
134
141
  if choice_function is not None:
135
142
  command += f"--function {choice_function} "
136
143
 
137
- if args.optimized: command = command.replace("python ", "python -OO ")
144
+ if args.optimized:
145
+ command = command.replace("python ", "python -OO ")
138
146
 
139
147
  from rich.panel import Panel
140
148
  from rich.console import Console
141
149
  from rich.syntax import Syntax
150
+
142
151
  console = Console()
143
152
  if args.zellij_tab is not None:
144
- comman_path__ = PathExtended.tmpfile(suffix=".sh")
153
+ comman_path__ = Path.home().joinpath(f"tmp_results/tmp_scripts/zellij_commands/{choice_file.stem}_{randstr()}.sh")
145
154
  comman_path__.parent.mkdir(parents=True, exist_ok=True)
146
155
  comman_path__.write_text(command, encoding="utf-8")
147
156
  console.print(Panel(Syntax(command, lexer="shell"), title=f"🔥 fire command @ {comman_path__}: "), style="bold red")
148
157
  import subprocess
158
+
149
159
  existing_tab_names = subprocess.run(["zellij", "action", "query-tab-names"], capture_output=True, text=True, check=True).stdout.splitlines()
150
160
  if args.zellij_tab in existing_tab_names:
151
161
  print(f"⚠️ Tab name `{args.zellij_tab}` already exists. Please choose a different name.")
152
162
  args.zellij_tab += f"_{randstr(3)}"
153
163
  from machineconfig.cluster.sessions_managers.zellij_local import run_command_in_zellij_tab
164
+
154
165
  command = run_command_in_zellij_tab(command=str(comman_path__), tab_name=args.zellij_tab, cwd=None)
155
166
  if args.watch:
156
167
  command = "watchexec --restart --exts py,sh,ps1 " + command
@@ -158,6 +169,7 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
158
169
  command = f"\ngit -C {choice_file.parent} pull\n" + command
159
170
  if args.PathExport:
160
171
  from machineconfig.scripts.python.helpers_fire_command.file_wrangler import add_to_path
172
+
161
173
  export_line = add_to_path(path_variable="PYTHONPATH", directory=str(repo_root))
162
174
  command = export_line + "\n" + command
163
175
  if args.loop:
@@ -169,6 +181,7 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
169
181
  raise NotImplementedError(f"Platform {platform.system()} not supported.")
170
182
 
171
183
  from machineconfig.utils.code import exit_then_run_shell_script
184
+
172
185
  exit_then_run_shell_script(script=command, strict=False)
173
186
 
174
187
 
@@ -187,15 +200,14 @@ def fire(
187
200
  module: Annotated[bool, typer.Option("--module", "-m", help="Launch the main file")] = False,
188
201
  optimized: Annotated[bool, typer.Option("--optimized", "-O", help="Run the optimized version of the function")] = False,
189
202
  zellij_tab: Annotated[Optional[str], typer.Option("--zellij-tab", "-z", help="Open in a new zellij tab")] = None,
190
-
191
203
  submit_to_cloud: Annotated[bool, typer.Option("--submit-to-cloud", "-C", help="Submit to cloud compute")] = False,
192
204
  remote: Annotated[bool, typer.Option("--remote", "-r", help="Launch on a remote machine")] = False,
193
-
194
205
  streamlit: Annotated[bool, typer.Option("--streamlit", "-S", help="Run as streamlit app")] = False,
195
206
  environment: Annotated[str, typer.Option("--environment", "-E", help="Choose ip, localhost, hostname or arbitrary url")] = "",
196
- holdDirectory: Annotated[bool, typer.Option("--holdDirectory", "-D", help="Hold current directory and avoid cd'ing to the script directory")] = False,
207
+ holdDirectory: Annotated[
208
+ bool, typer.Option("--holdDirectory", "-D", help="Hold current directory and avoid cd'ing to the script directory")
209
+ ] = False,
197
210
  PathExport: Annotated[bool, typer.Option("--PathExport", "-P", help="Augment the PYTHONPATH with repo root")] = False,
198
-
199
211
  git_pull: Annotated[bool, typer.Option("--git-pull", "-g", help="Start by pulling the git repo")] = False,
200
212
  watch: Annotated[bool, typer.Option("--watch", "-w", help="Watch the file for changes")] = False,
201
213
  ) -> None:
@@ -235,12 +247,14 @@ def fire(
235
247
  except Exception as e:
236
248
  # For other exceptions, print clean error message and exit
237
249
  import sys
250
+
238
251
  print(f"❌ Error: {e}", file=sys.stderr)
239
252
  sys.exit(1)
240
253
 
241
254
 
242
255
  def get_app():
243
256
  from typer import Typer
257
+
244
258
  app = Typer(add_completion=False)
245
259
  app.command(context_settings={"allow_extra_args": True, "allow_interspersed_args": False})(fire)
246
260
  return app
@@ -145,8 +145,10 @@ def ftpx(
145
145
  received_file = PathExtended(resolved_target) # type: ignore
146
146
  else:
147
147
  if source_is_remote:
148
- assert resolved_source is not None, """
149
- ❌ Path Error: Source must be a remote path (machine:path)"""
148
+ if resolved_source is None:
149
+ typer.echo("""❌ Path Error: Source must be a remote path (machine:path)""")
150
+ typer.Exit(code=1)
151
+ return
150
152
  target_display = resolved_target or "<auto>"
151
153
  console.print(
152
154
  Panel(
@@ -5,22 +5,10 @@ from machineconfig.scripts.python.helpers_agents.fire_agents_helper_types import
5
5
 
6
6
 
7
7
  def fire_qwen(ai_spec: AI_SPEC, prompt_path: Path, repo_root: Path, config_dir: str | None) -> str:
8
- # assert model == "qwen", "Only qwen is supported currently."
9
- # assert provider == "qwen", "Only qwen is supported currently."
10
- # model = "qwen"
11
- # model = "gemini-2.5-flash-lite"
12
- # model = None # auto-select
13
- # if model is None:
14
- # model_arg = ""
15
- # else:
16
8
  _ = ai_spec["provider"]
17
- # model_arg = f"--model {shlex.quote(model)}"
18
- # Need a real shell for the pipeline; otherwise '| gemini ...' is passed as args to 'cat'
19
9
  safe_path = shlex.quote(str(prompt_path))
20
-
21
10
  match ai_spec["machine"]:
22
11
  case "local":
23
- # Export the environment variable so it's available to subshells
24
12
  cmd = f"""
25
13
  qwen --yolo --prompt {safe_path}
26
14
  """
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- def code(path: str, title: str):
3
+ def get_read_python_file_pycode(path: str, title: str):
4
4
  from pathlib import Path
5
5
  print("Reading code from path:", path)
6
6
  pycode = Path(path).read_text(encoding="utf-8")
@@ -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
@@ -7,7 +7,7 @@ import machineconfig.scripts.python.helpers_devops.cli_config_dotfile as dotfile
7
7
  import machineconfig.profile.create_links_export as create_links_export
8
8
 
9
9
 
10
- def shell(which: Annotated[Literal["default", "d", "nushell", "n"], typer.Option(..., "--which", "-w", help="Which shell profile to create/configure")]="default"):
10
+ def configure_shell_profile(which: Annotated[Literal["default", "d", "nushell", "n"], typer.Option(..., "--which", "-w", help="Which shell profile to create/configure")]="default"):
11
11
  """🔗 Configure your shell profile."""
12
12
  from machineconfig.profile.create_shell_profile import create_default_shell_profile, create_nu_shell_profile
13
13
  match which:
@@ -20,20 +20,6 @@ def shell(which: Annotated[Literal["default", "d", "nushell", "n"], typer.Option
20
20
  typer.echo(f"[red]Error:[/] Unknown shell profile type: {which}")
21
21
 
22
22
 
23
- def path():
24
- """📚 NAVIGATE PATH variable with TUI"""
25
- from machineconfig.scripts.python import env_manager as navigator
26
- from pathlib import Path
27
- path = Path(navigator.__file__).resolve().parent.joinpath("path_manager_tui.py")
28
- from machineconfig.utils.code import run_shell_script, get_uv_command_executing_python_script
29
- uv_with = ["textual"]
30
- uv_project_dir = None
31
- if not Path.home().joinpath("code/machineconfig").exists():
32
- uv_with.append("machineconfig>=7.49")
33
- else:
34
- uv_project_dir = str(Path.home().joinpath("code/machineconfig"))
35
- run_shell_script(get_uv_command_executing_python_script(python_script=path.read_text(encoding="utf-8"), uv_with=uv_with, uv_project_dir=uv_project_dir)[0])
36
-
37
23
 
38
24
  def pwsh_theme():
39
25
  """🔗 Select powershell prompt theme."""
@@ -97,10 +83,8 @@ def get_app():
97
83
  config_apps.command("b", no_args_is_help=True, help="Manage public configuration files.", hidden=True)(create_links_export.main_public_from_parser)
98
84
  config_apps.command("dotfile", no_args_is_help=True, help="🔗 [d] Manage dotfiles.")(dotfile_module.main)
99
85
  config_apps.command("d", no_args_is_help=True, hidden=True)(dotfile_module.main)
100
- config_apps.command("shell", no_args_is_help=False, help="🔗 [s] Configure your shell profile.")(shell)
101
- config_apps.command("s", no_args_is_help=False, help="Configure your shell profile.", hidden=True)(shell)
102
- config_apps.command("path", no_args_is_help=False, help="📚 [p] NAVIGATE PATH variable with TUI")(path)
103
- config_apps.command("p", no_args_is_help=False, help="NAVIGATE PATH variable with TUI", hidden=True)(path)
86
+ config_apps.command("shell", no_args_is_help=False, help="🔗 [s] Configure your shell profile.")(configure_shell_profile)
87
+ config_apps.command("s", no_args_is_help=False, help="Configure your shell profile.", hidden=True)(configure_shell_profile)
104
88
  config_apps.command("starship-theme", no_args_is_help=False, help="🔗 [t] Select starship prompt theme.")(starship_theme)
105
89
  config_apps.command("t", no_args_is_help=False, help="Select starship prompt theme.", hidden=True)(starship_theme)
106
90
  config_apps.command("pwsh-theme", no_args_is_help=False, help="🔗 [T] Select powershell prompt theme.")(pwsh_theme)
@@ -12,7 +12,9 @@ def main(
12
12
  method: Annotated[Literal["symlink", "s", "copy", "c"], typer.Option(..., "--method", "-m", help="Method to use for linking files")] = "copy",
13
13
  on_conflict: Annotated[ON_CONFLICT_LOOSE, typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
14
14
  sensitivity: Annotated[Literal["private", "v", "public", "b"], typer.Option(..., "--sensitivity", "-s", help="Sensitivity of the config file.")] = "private",
15
- destination: Annotated[str, typer.Option("--destination", "-d", help="destination folder (override the default, use at your own risk)")] = "",) -> None:
15
+ destination: Annotated[str, typer.Option("--destination", "-d", help="destination folder (override the default, use at your own risk)")] = "",
16
+ shared: Annotated[bool, typer.Option("--shared", "-sh", help="Whether the config file is shared across destinations directory.")] = False,
17
+ ) -> None:
16
18
  from rich.console import Console
17
19
  from rich.panel import Panel
18
20
  from machineconfig.utils.links import symlink_map, copy_map
@@ -27,26 +29,33 @@ def main(
27
29
  console = Console()
28
30
  orig_path = Path(file).expanduser().absolute()
29
31
  if destination == "":
30
- new_path = backup_root.joinpath(orig_path.relative_to(Path.home()))
31
- new_path.parent.mkdir(parents=True, exist_ok=True)
32
+ if shared:
33
+ new_path = backup_root.joinpath("shared").joinpath(orig_path.name)
34
+ new_path.parent.mkdir(parents=True, exist_ok=True)
35
+ else:
36
+ new_path = backup_root.joinpath(orig_path.relative_to(Path.home()))
37
+ new_path.parent.mkdir(parents=True, exist_ok=True)
32
38
  else:
33
- dest_path = Path(destination).expanduser().absolute()
34
- dest_path.mkdir(parents=True, exist_ok=True)
35
- new_path = dest_path.joinpath(orig_path.name)
36
-
37
-
38
- from machineconfig.utils.path_extended import PathExtended
39
+ if shared:
40
+ dest_path = Path(destination).expanduser().absolute()
41
+ dest_path.mkdir(parents=True, exist_ok=True)
42
+ new_path = dest_path.joinpath("shared").joinpath(orig_path.name)
43
+ new_path.parent.mkdir(parents=True, exist_ok=True)
44
+ else:
45
+ dest_path = Path(destination).expanduser().absolute()
46
+ dest_path.mkdir(parents=True, exist_ok=True)
47
+ new_path = dest_path.joinpath(orig_path.name)
39
48
  match method:
40
49
  case "copy" | "c":
41
50
  try:
42
- 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]
43
52
  except Exception as e:
44
53
  typer.echo(f"[red]Error:[/] {e}")
45
54
  typer.Exit(code=1)
46
55
  return
47
56
  case "symlink" | "s":
48
57
  try:
49
- 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]
50
59
  except Exception as e:
51
60
  typer.echo(f"[red]Error:[/] {e}")
52
61
  typer.Exit(code=1)
@@ -56,10 +65,10 @@ def main(
56
65
 
57
66
  # mapper_snippet = "\n".join(
58
67
  # [
59
- # 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]",
60
69
  # "",
61
70
  # f"[{new_path.parent.name}]",
62
- # 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()}' }}",
63
72
  # ]
64
73
  # )
65
74
  # console.print(
@@ -9,7 +9,9 @@ def copy_both_assets():
9
9
  create_helper.copy_assets_to_machine(which="settings")
10
10
 
11
11
 
12
- def update(copy_assets: Annotated[bool, typer.Option("--assets-copy/--no-assets-copy", "-a/-na", help="Copy (overwrite) assets to the machine after the update")] = True):
12
+ def update(copy_assets: Annotated[bool, typer.Option("--assets-copy/--no-assets-copy", "-a/-na", help="Copy (overwrite) assets to the machine after the update")] = True,
13
+ link_public_configs: Annotated[bool, typer.Option("--link-public-configs/--no-link-public-configs", "-b/-nb", help="Link public configs after update (overwrites your configs!)")] = False,
14
+ ):
13
15
  """🔄 UPDATE uv and machineconfig"""
14
16
  from pathlib import Path
15
17
  if Path.home().joinpath("code", "machineconfig").exists():
@@ -34,8 +36,12 @@ uv tool install --upgrade machineconfig
34
36
  else:
35
37
  from machineconfig.utils.code import run_shell_script
36
38
  run_shell_script(shell_script)
37
- if copy_assets:
38
- copy_both_assets()
39
+ if copy_assets:
40
+ copy_both_assets()
41
+ if link_public_configs:
42
+ import machineconfig.profile.create_links_export as create_links_export
43
+ create_links_export.main_public_from_parser(method="copy", on_conflict="overwrite-default-path", which="all", interactive=False)
44
+
39
45
 
40
46
  def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-na", help="Copy (overwrite) assets to the machine after the update")] = False):
41
47
  """📋 CLONE machienconfig locally and incorporate to shell profile for faster execution and nightly updates."""
@@ -46,9 +52,9 @@ def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-n
46
52
  else:
47
53
  import platform
48
54
  if platform.system() == "Windows":
49
- run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.49" """)
55
+ run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.64" """)
50
56
  else:
51
- run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.49" """)
57
+ run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.64" """)
52
58
  from machineconfig.profile.create_shell_profile import create_default_shell_profile
53
59
  if not no_copy_assets:
54
60
  create_default_shell_profile() # involves copying assets too
@@ -73,7 +79,7 @@ def navigate():
73
79
  path = Path(navigator.__file__).resolve().parent.joinpath("devops_navigator.py")
74
80
  from machineconfig.utils.code import run_shell_script
75
81
  if Path.home().joinpath("code/machineconfig").exists(): executable = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with textual"""
76
- else: executable = """--with "machineconfig>=7.49,textual" """
82
+ else: executable = """--with "machineconfig>=7.64,textual" """
77
83
  run_shell_script(f"""uv run {executable} {path}""")
78
84
 
79
85
 
@@ -12,7 +12,7 @@ Usage examples:
12
12
  devops network receive -- --relay 10.17.62.206:443 7121-donor-olympic-bicycle
13
13
  devops network receive -- croc --relay 10.17.62.206:443 7121-donor-olympic-bicycle
14
14
  """
15
- from machineconfig.utils.installer_utils.installer import install_if_missing
15
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
16
16
  install_if_missing(which="croc")
17
17
  import platform
18
18
  import sys
@@ -100,7 +100,7 @@ def share_file_send(path: Annotated[str, typer.Argument(help="Path to the file o
100
100
  qrcode: Annotated[bool, typer.Option("--qrcode", "--qr", help="Show receive code as a qrcode")] = False,
101
101
  ) -> None:
102
102
  """Send a file using croc with relay server."""
103
- from machineconfig.utils.installer_utils.installer import install_if_missing
103
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
104
104
  install_if_missing(which="croc")
105
105
  # Get relay server IP from environment or use default
106
106
  import socket
@@ -40,7 +40,7 @@ def web_file_explorer(
40
40
  over_internet: Annotated[bool, typer.Option("--over-internet", "-i", help="Expose the share server over the internet using ngrok")] = False,
41
41
  backend: Annotated[str, typer.Option("--backend", "-b", help="Backend to use: filebrowser (default), miniserve, or easy-sharing")] = "filebrowser"
42
42
  ) -> None:
43
- from machineconfig.utils.installer_utils.installer import install_if_missing
43
+ from machineconfig.utils.installer_utils.installer_cli import install_if_missing
44
44
 
45
45
  if backend not in ["filebrowser", "miniserve", "easy-sharing"]:
46
46
  typer.echo(f"❌ ERROR: Invalid backend '{backend}'. Must be one of: filebrowser, miniserve, easy-sharing", err=True)
@@ -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
 
@@ -1,79 +1,7 @@
1
1
 
2
2
 
3
3
  import typer
4
- from typing import Annotated, Literal, Optional, TypedDict
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)
4
+ from typing import Annotated, Optional
77
5
 
78
6
 
79
7
  def merge_pdfs(
@@ -165,82 +93,3 @@ def compress_pdf(
165
93
  uv_command, _py_file = get_uv_command_executing_python_script(python_script=code, uv_with=["pymupdf"], uv_project_dir=None)
166
94
  run_shell_script(uv_command)
167
95
 
168
-
169
- class MachineSpecs(TypedDict):
170
- system: str
171
- distro: str
172
- home_dir: str
173
- def get_machine_specs() -> MachineSpecs:
174
- """Write print and return the local machine specs."""
175
- import platform
176
- UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
177
- command = f"""{UV_RUN_CMD} --with distro python -c "import distro; print(distro.name(pretty=True))" """
178
- import subprocess
179
- distro = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
180
- specs: MachineSpecs = {
181
- "system": platform.system(),
182
- "distro": distro,
183
- "home_dir": str(Path.home()),
184
- }
185
- print(specs)
186
- from machineconfig.utils.source_of_truth import CONFIG_ROOT
187
- path = CONFIG_ROOT.joinpath("machine_specs.json")
188
- CONFIG_ROOT.mkdir(parents=True, exist_ok=True)
189
- import json
190
- path.write_text(json.dumps(specs, indent=4), encoding="utf-8")
191
- return specs
192
-
193
-
194
- def init_project(python: Annotated[Literal["3.13", "3.14"], typer.Option("--python", "-p", help="Python version for the uv virtual environment.")]= "3.13") -> None:
195
- _ = python
196
- repo_root = Path.cwd()
197
- if not (repo_root / "pyproject.toml").exists():
198
- typer.echo("❌ Error: pyproject.toml not found.", err=True)
199
- raise typer.Exit(code=1)
200
- print("Adding group `plot` with common data science and plotting packages...")
201
- script = """
202
- uv add --group plot \
203
- # Data & computation
204
- numpy pandas polars duckdb-engine python-magic \
205
- # Plotting / visualization
206
- matplotlib plotly kaleido \
207
- # Notebooks / interactive
208
- ipython ipykernel jupyterlab nbformat marimo \
209
- # Code analysis / type checking / linting
210
- mypy pyright ruff pylint pyrefly \
211
- # Packaging / build / dev
212
- cleanpy \
213
- # CLI / debugging / utilities
214
- ipdb pudb \
215
- # Type hints for packages
216
- types-python-dateutil types-pyyaml types-requests types-tqdm \
217
- types-mysqlclient types-paramiko types-pytz types-sqlalchemy types-toml types-urllib3 \
218
-
219
- """
220
- from machineconfig.utils.code import run_shell_script
221
- run_shell_script(script)
222
-
223
-
224
- def edit(path: Annotated[Optional[str], typer.Argument(..., help="The root directory of the project to edit, or a file path.")] = None) -> None:
225
- if path is None:
226
- root_path = Path.cwd()
227
- print(f"No path provided. Using current working directory: {root_path}")
228
- else:
229
- root_path = Path(path).expanduser().resolve()
230
- print(f"Using provided path: {root_path}")
231
- from machineconfig.utils.accessories import get_repo_root
232
- repo_root = get_repo_root(root_path)
233
- if repo_root is not None and repo_root.joinpath("pyproject.toml").exists():
234
- code = f"""
235
- cd {repo_root}
236
- uv add --dev pylsp-mypy python-lsp-server[all] pyright ruff-lsp # for helix editor.
237
- source ./.venv/bin/activate
238
- """
239
- else:
240
- code = ""
241
- if root_path.is_file():
242
- code += f"hx {root_path}"
243
- else:
244
- code += "hx"
245
- from machineconfig.utils.code import exit_then_run_shell_script
246
- exit_then_run_shell_script(code)