machineconfig 7.38__py3-none-any.whl → 7.39__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of machineconfig might be problematic. Click here for more details.

Files changed (31) hide show
  1. machineconfig/profile/create_links.py +2 -1
  2. machineconfig/profile/create_links_export.py +37 -8
  3. machineconfig/profile/create_shell_profile.py +64 -124
  4. machineconfig/scripts/linux/wrap_mcfg +1 -1
  5. machineconfig/scripts/python/croshell.py +4 -4
  6. machineconfig/scripts/python/define.py +1 -1
  7. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  8. machineconfig/scripts/python/helpers_devops/cli_config.py +27 -32
  9. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +12 -9
  10. machineconfig/scripts/python/helpers_devops/cli_repos.py +11 -10
  11. machineconfig/scripts/python/helpers_devops/cli_self.py +5 -7
  12. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +34 -15
  13. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  14. machineconfig/scripts/python/machineconfig.py +7 -0
  15. machineconfig/scripts/python/terminal.py +20 -3
  16. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  17. machineconfig/scripts/windows/wrap_mcfg.ps1 +5 -0
  18. machineconfig/settings/shells/nushell/config.nu +1 -31
  19. machineconfig/settings/shells/nushell/init.nu +100 -34
  20. machineconfig/settings/shells/wt/settings.json +10 -2
  21. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  22. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
  23. machineconfig/utils/code.py +1 -1
  24. machineconfig/utils/links.py +3 -2
  25. machineconfig/utils/ssh.py +1 -1
  26. {machineconfig-7.38.dist-info → machineconfig-7.39.dist-info}/METADATA +1 -1
  27. {machineconfig-7.38.dist-info → machineconfig-7.39.dist-info}/RECORD +30 -31
  28. machineconfig/scripts/python/helpers_repos/secure_repo.py +0 -15
  29. {machineconfig-7.38.dist-info → machineconfig-7.39.dist-info}/WHEEL +0 -0
  30. {machineconfig-7.38.dist-info → machineconfig-7.39.dist-info}/entry_points.txt +0 -0
  31. {machineconfig-7.38.dist-info → machineconfig-7.39.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from rich.table import Table
13
13
 
14
14
  from machineconfig.utils.path_extended import PathExtended
15
15
  from machineconfig.utils.links import symlink_map, copy_map
16
+ from machineconfig.profile.create_links_export import ON_CONFLICT_STRICT
16
17
  from machineconfig.utils.source_of_truth import LIBRARY_ROOT, CONFIG_ROOT
17
18
 
18
19
  import platform
@@ -79,7 +80,7 @@ def read_mapper() -> MapperFileData:
79
80
 
80
81
 
81
82
  def apply_mapper(mapper_data: dict[str, list[ConfigMapper]],
82
- on_conflict: Literal["throw-error", "overwrite-self-managed", "backup-self-managed", "overwrite-default-path", "backup-default-path"],
83
+ on_conflict: ON_CONFLICT_STRICT,
83
84
  method: Literal["symlink", "copy"]
84
85
  ):
85
86
  operation_records: list[OperationRecord] = []
@@ -1,10 +1,26 @@
1
1
 
2
2
  import typer
3
- from typing import Optional, Literal, Annotated
3
+ from typing import Optional, Literal, Annotated, TypeAlias
4
4
 
5
+ ON_CONFLICT_LOOSE: TypeAlias = Literal[
6
+ "throw-error", "t",
7
+ "overwrite-self-managed", "os",
8
+ "backup-self-managed", "bs",
9
+ "overwrite-default-path", "od",
10
+ "backup-default-path", "bd"
11
+ ]
12
+ ON_CONFLICT_STRICT: TypeAlias = Literal["throw-error", "overwrite-self-managed", "backup-self-managed", "overwrite-default-path", "backup-default-path"]
13
+ ON_CONFLICT_MAPPER: dict[str, ON_CONFLICT_STRICT] = {
14
+ "t": "throw-error",
15
+ "os": "overwrite-self-managed",
16
+ "bs": "backup-self-managed",
17
+ "od": "overwrite-default-path",
18
+ "bd": "backup-default-path"
19
+ }
5
20
 
6
- def main_public_from_parser(method: Annotated[Literal["symlink", "copy"], typer.Option(..., help="Method to use for setting up the config file.")],
7
- on_conflict: Annotated[Literal["throw-error", "overwrite-default-path", "backup-default-path"], typer.Option(..., help="Action to take on conflict")],
21
+
22
+ def main_public_from_parser(method: Annotated[Literal["symlink", "s", "copy", "c"], typer.Option(..., help="Method to use for setting up the config file.")],
23
+ on_conflict: Annotated[ON_CONFLICT_LOOSE, typer.Option(..., help="Action to take on conflict")],
8
24
  which: Annotated[Optional[str], typer.Option(..., help="Specific items to process")] = None,
9
25
  interactive: Annotated[bool, typer.Option(..., help="Run in interactive mode")] = False):
10
26
  """Terminology:
@@ -28,15 +44,21 @@ def main_public_from_parser(method: Annotated[Literal["symlink", "copy"], typer.
28
44
  from machineconfig.profile.create_links import apply_mapper
29
45
  from machineconfig.profile.create_helper import copy_assets_to_machine
30
46
  copy_assets_to_machine(which="settings") # config files live here and will be linked to.
31
- apply_mapper(mapper_data=items_objections, on_conflict=on_conflict, method=method)
47
+ method_map: dict[str, Literal["symlink", "copy"]] = {
48
+ "s": "symlink",
49
+ "symlink": "symlink",
50
+ "c": "copy",
51
+ "copy": "copy",
52
+ }
53
+ method = method_map[method]
54
+ apply_mapper(mapper_data=items_objections, on_conflict=ON_CONFLICT_MAPPER[on_conflict], method=method)
32
55
 
33
56
 
34
- def main_private_from_parser(method: Annotated[Literal["symlink", "copy"], typer.Option(..., help="Method to use for linking files")],
35
- on_conflict: Annotated[Literal["throw-error", "overwrite-self-managed", "backup-self-managed", "overwrite-default-path", "backup-default-path"], typer.Option(..., help="Action to take on conflict")] = "throw-error",
57
+ def main_private_from_parser(method: Annotated[Literal["symlink", "s", "copy", "c"], typer.Option(..., help="Method to use for linking files")],
58
+ on_conflict: Annotated[ON_CONFLICT_LOOSE, typer.Option(..., help="Action to take on conflict")] = "throw-error",
36
59
  which: Annotated[Optional[str], typer.Option(..., help="Specific items to process")] = None,
37
60
  interactive: Annotated[bool, typer.Option(..., help="Run in interactive mode")] = False):
38
61
  from machineconfig.profile.create_links import ConfigMapper, read_mapper
39
-
40
62
  mapper_full = read_mapper()["private"]
41
63
  if which is None:
42
64
  assert interactive is True
@@ -51,4 +73,11 @@ def main_private_from_parser(method: Annotated[Literal["symlink", "copy"], typer
51
73
  items_objections: dict[str, list[ConfigMapper]] = {item: mapper_full[item] for item in items_chosen if item in mapper_full}
52
74
 
53
75
  from machineconfig.profile.create_links import apply_mapper
54
- apply_mapper(mapper_data=items_objections, on_conflict=on_conflict, method=method)
76
+ method_map: dict[str, Literal["symlink", "copy"]] = {
77
+ "s": "symlink",
78
+ "symlink": "symlink",
79
+ "c": "copy",
80
+ "copy": "copy",
81
+ }
82
+ method = method_map[method]
83
+ apply_mapper(mapper_data=items_objections, on_conflict=ON_CONFLICT_MAPPER[on_conflict], method=method)
@@ -1,23 +1,16 @@
1
1
  """shell"""
2
2
 
3
- from machineconfig.utils.path_extended import PathExtended
4
- from machineconfig.utils.source_of_truth import CONFIG_ROOT
5
3
  from pathlib import Path
6
- import platform
7
- import os
8
- import subprocess
9
- from rich.console import Console
10
- from rich.panel import Panel
11
4
 
12
5
 
13
- system = platform.system()
14
- sep = ";" if system == "Windows" else ":" # PATH separator, this is special for PATH object, not to be confused with PathExtended.sep (normal paths), usually / or \
15
- PATH = os.environ["PATH"].split(sep)
16
- console = Console()
17
- BOX_WIDTH = 100 # Define BOX_WIDTH or get it from a config
18
-
19
6
 
20
7
  def get_shell_profile_path() -> Path:
8
+ import platform
9
+ import subprocess
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ system = platform.system()
13
+ console = Console()
21
14
  if system == "Windows":
22
15
  result = subprocess.run(["pwsh", "-Command", "$PROFILE"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
23
16
  if result.returncode == 0 and result.stdout.strip():
@@ -37,8 +30,34 @@ def get_shell_profile_path() -> Path:
37
30
  return profile_path
38
31
 
39
32
 
33
+ def get_nu_shell_profile_path() -> Path:
34
+ import platform
35
+ from rich.console import Console
36
+ from rich.panel import Panel
37
+ system = platform.system()
38
+ console = Console()
39
+ if system == "Windows":
40
+ profile_path = Path.home().joinpath(r"AppData\Roaming\nushell")
41
+ elif system == "Linux":
42
+ profile_path = Path.home().joinpath(".config/nushell")
43
+ elif system == "Darwin":
44
+ profile_path = Path.home().joinpath("Library/Application Support/nushell")
45
+ else:
46
+ raise ValueError(f"""Not implemented for this system {system}""")
47
+ console.print(Panel(f"""🐚 NU SHELL PROFILE | Working with path: `{profile_path}`""", title="[bold cyan]Nu Shell Profile[/bold cyan]", border_style="cyan"))
48
+ return profile_path
49
+
50
+
40
51
  def create_default_shell_profile() -> None:
41
52
  shell_profile_path = get_shell_profile_path()
53
+ import platform
54
+ import subprocess
55
+ from rich.console import Console
56
+ from rich.panel import Panel
57
+ from machineconfig.utils.source_of_truth import CONFIG_ROOT
58
+ from machineconfig.utils.path_extended import PathExtended
59
+ system = platform.system()
60
+ console = Console()
42
61
  if not shell_profile_path.exists():
43
62
  console.print(Panel(f"""🆕 PROFILE | Profile does not exist at `{shell_profile_path}`. Creating a new one.""", title="[bold blue]Profile[/bold blue]", border_style="blue"))
44
63
  shell_profile_path.parent.mkdir(parents=True, exist_ok=True)
@@ -79,117 +98,38 @@ def create_default_shell_profile() -> None:
79
98
  console.print(Panel("✅ Profile updated successfully", title="[bold blue]Profile[/bold blue]", border_style="blue"))
80
99
 
81
100
 
82
- # def append_temporarily(dirs: list[str], kind: Literal["append", "prefix", "replace"]) -> str:
83
- # dirs_ = []
84
- # for path in dirs:
85
- # path_rel = PathExtended(path).collapseuser(strict=False)
86
- # if path_rel.as_posix() in PATH or str(path_rel) in PATH or str(path_rel.expanduser()) in PATH or path_rel.expanduser().as_posix() in PATH:
87
- # print(f"Path passed `{path}` is already in PATH, skipping the appending.")
88
- # else:
89
- # dirs_.append(path_rel.as_posix() if system == "Linux" else str(path_rel))
90
- # dirs = dirs_
91
- # if len(dirs) == 0:
92
- # return ""
93
-
94
- # if system == "Windows":
95
- # """Source: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2"""
96
- # if kind == "append":
97
- # command = rf'$env:Path += ";{sep.join(dirs)}"' # Append to the Path variable in the current window:
98
- # elif kind == "prefix":
99
- # command = rf'$env:Path = "{sep.join(dirs)};" + $env:Path' # Prefix the Path variable in the current window:
100
- # elif kind == "replace":
101
- # command = rf'$env:Path = "{sep.join(dirs)}"' # Replace the Path variable in the current window (use with caution!):
102
- # else:
103
- # raise KeyError
104
- # return command # if run is False else tm.run(command, shell="powershell")
105
- # elif system in ["Linux", "Darwin"]:
106
- # result = f'export PATH="{sep.join(dirs)}:$PATH"'
107
- # else:
108
- # raise ValueError
109
- # return result
110
-
111
-
112
- # def main_add_sources_to_shell_profile(profile_path: Optional[str], choice: Optional[str]) -> None:
113
- # sources: list[str] = read_toml(LIBRARY_ROOT.joinpath("profile/sources.toml"))[system.lower()]["files"]
114
- # console.print(Panel("🔄 Adding sources to shell profile", title="[bold blue]Sources[/bold blue]", border_style="blue"))
115
-
116
- # if choice is None:
117
- # choice_obj = choose_from_options(msg="Which patch to add?", options=sources + ["all", "none(EXIT)"], default="none(EXIT)", multi=True)
118
- # if isinstance(choice_obj, str):
119
- # if choice_obj == "all":
120
- # choice = choice_obj
121
- # elif choice_obj == "none(EXIT)":
122
- # return
123
- # else:
124
- # sources = [choice_obj]
125
- # else: # isinstance(choice_obj, list):
126
- # sources = choice_obj
127
- # elif choice == "none(EXIT)":
128
- # return
129
-
130
- # if isinstance(profile_path, str):
131
- # profile_path_obj = PathExtended(profile_path)
132
- # else:
133
- # profile_path_obj = get_shell_profile_path()
134
- # profile = profile_path_obj.read_text(encoding="utf-8")
135
-
136
- # for a_file in sources:
137
- # tmp = a_file.replace("REPO_ROOT", REPO_ROOT.as_posix()).replace("LIBRARY_ROOT", LIBRARY_ROOT.as_posix())
138
- # file = PathExtended(tmp).collapseuser() # this makes the shell profile interuseable across machines.
139
- # file = file.as_posix() if system == "Linux" else str(file)
140
- # if file not in profile:
141
- # if system == "Windows":
142
- # profile += f"\n. {file}"
143
- # console.print(f"➕ Added PowerShell source: {file}")
144
- # elif system == "Linux":
145
- # profile += f"\nsource {file}"
146
- # console.print(f"➕ Added Bash source: {file}")
147
- # else:
148
- # raise ValueError(f"Not implemented for this system {system}")
149
- # else:
150
- # console.print(f"⏭️ Source already present: {file}")
151
-
152
- # profile_path_obj.write_text(profile, encoding="utf-8")
153
- # console.print(Panel("✅ Shell profile updated with sources", title="[bold blue]Sources[/bold blue]", border_style="blue"))
154
-
155
-
156
- # def main_add_patches_to_shell_profile(profile_path: Optional[str], choice: Optional[str]) -> None:
157
- # patches: list[str] = [item.as_posix() for item in PathExtended(LIBRARY_ROOT).joinpath(f"profile/patches/{system.lower()}").search()]
158
-
159
- # console.print(Panel("🩹 Adding patches to shell profile", title="[bold blue]Patches[/bold blue]", border_style="blue"))
160
-
161
- # if choice is None:
162
- # choice_chosen = choose_from_options(msg="Which patch to add?", options=list(patches) + ["all", "none(EXIT)"], default="none(EXIT)", multi=False)
163
- # assert isinstance(choice_chosen, str), f"Choice must be a string or a list of strings, not {type(choice)}"
164
- # choice = choice_chosen
165
- # if choice == "none(EXIT)":
166
- # return None
167
- # elif str(choice) == "all":
168
- # console.print("📌 Adding all patches to profile")
169
- # else:
170
- # patches = [choice]
171
- # console.print(f"📌 Adding selected patch: {choice}")
172
-
173
- # profile_path_obj = PathExtended(profile_path) if isinstance(profile_path, str) else get_shell_profile_path()
174
- # profile = profile_path_obj.read_text(encoding="utf-8")
175
-
176
- # for patch_path in patches:
177
- # patch_path_obj = PathExtended(patch_path)
178
- # patch = patch_path_obj.read_text(encoding="utf-8")
179
- # if patch in profile:
180
- # console.print(f"⏭️ Patch already present: {patch_path_obj.name}")
181
- # else:
182
- # profile += "\n" + patch
183
- # console.print(f"➕ Added patch: {patch_path_obj.name}")
184
-
185
- # if system == "Linux":
186
- # res = Terminal().run("cat /proc/version").op
187
- # if "microsoft" in res.lower() or "wsl" in res.lower():
188
- # profile += "\ncd ~" # this is to make sure that the current dir is not in the windows file system, which is terribly slow and its a bad idea to be there anyway.
189
- # console.print("📌 WSL detected - adding 'cd ~' to profile to avoid Windows filesystem")
190
-
191
- # profile_path_obj.write_text(profile, encoding="utf-8")
192
- # console.print(Panel("✅ Shell profile updated with patches", title="[bold blue]Patches[/bold blue]", border_style="blue"))
101
+ def create_nu_shell_profile() -> None:
102
+ from rich.console import Console
103
+ from rich.panel import Panel
104
+ from machineconfig.utils.source_of_truth import CONFIG_ROOT
105
+ from machineconfig.utils.path_extended import PathExtended
106
+ console = Console()
107
+ nu_profile_path = get_nu_shell_profile_path()
108
+ config_dir = nu_profile_path
109
+ config_file = config_dir.joinpath("config.nu")
110
+ if not config_dir.exists():
111
+ console.print(Panel(f"""🆕 NU SHELL CONFIG | Config directory does not exist at `{config_dir}`. Creating a new one.""", title="[bold cyan]Nu Shell Config[/bold cyan]", border_style="cyan"))
112
+ config_dir.mkdir(parents=True, exist_ok=True)
113
+ if not config_file.exists():
114
+ console.print(Panel(f"""🆕 NU SHELL CONFIG | config.nu file does not exist at `{config_file}`. Creating a new one.""", title="[bold cyan]Nu Shell Config[/bold cyan]", border_style="cyan"))
115
+ config_file.write_text("", encoding="utf-8")
116
+ config_content = config_file.read_text(encoding="utf-8")
117
+ from machineconfig.profile.create_helper import copy_assets_to_machine
118
+ copy_assets_to_machine("settings")
119
+ copy_assets_to_machine("scripts")
120
+ init_script = PathExtended(CONFIG_ROOT).joinpath("settings/shells/nushell/init.nu")
121
+ source_line = f"""use {str(init_script)}"""
122
+ was_config_updated = False
123
+ if source_line in config_content:
124
+ console.print(Panel("🔄 NU SHELL CONFIG | Skipping init script sourcing - already present in config.nu", title="[bold cyan]Nu Shell Config[/bold cyan]", border_style="cyan"))
125
+ else:
126
+ console.print(Panel("📝 NU SHELL CONFIG | Adding init script sourcing to config.nu", title="[bold cyan]Nu Shell Config[/bold cyan]", border_style="cyan"))
127
+ config_content += "\n" + source_line + "\n"
128
+ was_config_updated = True
129
+ if was_config_updated:
130
+ config_dir.mkdir(parents=True, exist_ok=True)
131
+ config_file.write_text(config_content, encoding="utf-8")
132
+ console.print(Panel("✅ Nu shell config updated successfully", title="[bold cyan]Nu Shell Config[/bold cyan]", border_style="cyan"))
193
133
 
194
134
 
195
135
  if __name__ == "__main__":
@@ -27,7 +27,7 @@ wrap_in_shell_script() {
27
27
 
28
28
  if [[ -f "$OP_PROGRAM_PATH" ]]; then
29
29
  printf "%b\n" "${GREEN}🚀 Taking over from python script @ ${OP_PROGRAM_PATH}${RESET}"
30
- bat --style=plain --paging=never "$OP_PROGRAM_PATH"
30
+ bat --style=full --theme=OneHalfDark --paging=never "$OP_PROGRAM_PATH"
31
31
  printf "%b\n" "${GREEN}▶ Running...${RESET}"
32
32
  . "$OP_PROGRAM_PATH"
33
33
  status=$?
@@ -130,7 +130,7 @@ def croshell(
130
130
  fire_line = f"uv run --python 3.14 --with visidata,pyarrow vd {str(file_obj)}"
131
131
  elif marimo:
132
132
  if Path.home().joinpath("code/machineconfig").exists(): requirements = f"""--with marimo --project "{str(Path.home().joinpath("code/machineconfig"))}" """
133
- else: requirements = """--python 3.14 --with "marimo,machineconfig[plot]>=7.38" """
133
+ else: requirements = """--python 3.14 --with "marimo,machineconfig[plot]>=7.39" """
134
134
  fire_line = f"""
135
135
  cd {str(pyfile.parent)}
136
136
  uv run --python 3.14 --with "marimo" marimo convert {pyfile.name} -o marimo_nb.py
@@ -138,14 +138,14 @@ uv run {requirements} marimo edit --host 0.0.0.0 marimo_nb.py
138
138
  """
139
139
  elif jupyter:
140
140
  if Path.home().joinpath("code/machineconfig").exists(): requirements = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with jupyterlab """
141
- else: requirements = """--with "machineconfig[plot]>=7.38" """
141
+ else: requirements = """--with "machineconfig[plot]>=7.39" """
142
142
  fire_line = f"uv run {requirements} jupyter-lab {str(nb_target)}"
143
143
  elif vscode:
144
144
  fire_line = f"""
145
145
  cd {str(pyfile.parent)}
146
146
  uv init --python 3.14
147
147
  uv venv
148
- uv add "machineconfig[plot]>=7.38"
148
+ uv add "machineconfig[plot]>=7.39"
149
149
  # code serve-web
150
150
  code --new-window {str(pyfile)}
151
151
  """
@@ -153,7 +153,7 @@ code --new-window {str(pyfile)}
153
153
  if interpreter == "ipython": profile = f" --profile {ipython_profile} --no-banner"
154
154
  else: profile = ""
155
155
  if Path.home().joinpath("code/machineconfig").exists(): ve_line = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" """
156
- else: ve_line = """--python 3.14 --with "machineconfig[plot]>=7.38" """
156
+ else: ve_line = """--python 3.14 --with "machineconfig[plot]>=7.39" """
157
157
  # ve_path_maybe, ipython_profile_maybe = get_ve_path_and_ipython_profile(Path.cwd())
158
158
  # --python 3.14
159
159
  fire_line = f"uv run {ve_line} {interpreter} {interactivity} {profile} {str(pyfile)}"
@@ -5,10 +5,10 @@ Minimalist programs that only print scripts without frills so it can be sourced
5
5
 
6
6
 
7
7
  import typer
8
- import platform
9
8
 
10
9
 
11
10
  def define_scripts():
11
+ import platform
12
12
  if platform.system() != "Linux":
13
13
  raise RuntimeError("This command is only supported on Linux systems.")
14
14
  from machineconfig.setup_linux import INTERACTIVE as script_path
@@ -2,7 +2,7 @@
2
2
  # /// script
3
3
  # requires-python = ">=3.13"
4
4
  # dependencies = [
5
- # "machineconfig>=7.38",
5
+ # "machineconfig>=7.39",
6
6
  # "textual",
7
7
  # "pyperclip",
8
8
  # ]
@@ -1,33 +1,22 @@
1
1
 
2
2
 
3
- from typing import Literal, Annotated, Optional
3
+ from typing import Literal, Annotated
4
4
  from pathlib import Path
5
5
  import typer
6
6
  import machineconfig.scripts.python.helpers_devops.cli_config_dotfile as dotfile_module
7
+ import machineconfig.profile.create_links_export as create_links_export
7
8
 
8
-
9
- def private(method: Annotated[Literal["symlink", "copy"], typer.Option(..., "--method", "-m", help="Method to use for linking files")],
10
- on_conflict: Annotated[Literal["throw-error", "overwrite-self-managed", "backup-self-managed", "overwrite-default-path", "backup-default-path"], typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
11
- which: Annotated[Optional[str], typer.Option(..., "--which", "-w", help="Specific items to process")] = None,
12
- interactive: Annotated[bool, typer.Option(..., "--interactive", "-ia", help="Run in interactive mode")] = False):
13
- """🔗 Manage private configuration files."""
14
- import machineconfig.profile.create_links_export as create_links_export
15
- create_links_export.main_private_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
16
-
17
-
18
- def public(method: Annotated[Literal["symlink", "copy"], typer.Option(..., "--method", "-m", help="Method to use for setting up the config file.")],
19
- on_conflict: Annotated[Literal["throw-error", "overwrite-default-path", "backup-default-path"], typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
20
- which: Annotated[Optional[str], typer.Option(..., "--which", "-w", help="Specific items to process")] = None,
21
- interactive: Annotated[bool, typer.Option(..., "--interactive", "-ia", help="Run in interactive mode")] = False):
22
- """🔗 Manage public configuration files."""
23
- import machineconfig.profile.create_links_export as create_links_export
24
- create_links_export.main_public_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
25
-
26
-
27
- def shell():
9
+ def shell(which: Annotated[Literal["default", "d", "nushell", "n"], typer.Option(..., "--which", "-w", help="Which shell profile to create/configure")]="default"):
28
10
  """🔗 Configure your shell profile."""
29
- from machineconfig.profile.create_shell_profile import create_default_shell_profile
30
- create_default_shell_profile()
11
+ from machineconfig.profile.create_shell_profile import create_default_shell_profile, create_nu_shell_profile
12
+ match which:
13
+ case "nushell" | "n":
14
+ create_nu_shell_profile()
15
+ return
16
+ case "default" | "d":
17
+ create_default_shell_profile()
18
+ return
19
+ typer.echo(f"[red]Error:[/] Unknown shell profile type: {which}")
31
20
 
32
21
 
33
22
  def path():
@@ -39,7 +28,7 @@ def path():
39
28
  uv_with = ["textual"]
40
29
  uv_project_dir = None
41
30
  if not Path.home().joinpath("code/machineconfig").exists():
42
- uv_with.append("machineconfig>=7.38")
31
+ uv_with.append("machineconfig>=7.39")
43
32
  else:
44
33
  uv_project_dir = str(Path.home().joinpath("code/machineconfig"))
45
34
  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])
@@ -82,23 +71,29 @@ def starship_theme():
82
71
  typer.echo("❌ Please enter a valid number")
83
72
 
84
73
 
85
- def copy_assets(which: Annotated[Literal["scripts", "settings", "both"], typer.Argument(..., help="Which assets to copy")]):
74
+ def copy_assets(which: Annotated[Literal["scripts", "s", "settings", "t", "both", "b"], typer.Argument(..., help="Which assets to copy")]):
86
75
  """🔗 Copy asset files from library to machine."""
87
76
  import machineconfig.profile.create_helper as create_helper
88
77
  match which:
89
- case "both":
78
+ case "both" | "b":
79
+ create_helper.copy_assets_to_machine(which="scripts")
80
+ create_helper.copy_assets_to_machine(which="settings")
81
+ return
82
+ case "scripts" | "s":
90
83
  create_helper.copy_assets_to_machine(which="scripts")
84
+ return
85
+ case "settings" | "t":
91
86
  create_helper.copy_assets_to_machine(which="settings")
92
- case _:
93
- create_helper.copy_assets_to_machine(which=which)
87
+ return
88
+ typer.echo(f"[red]Error:[/] Unknown asset type: {which}")
94
89
 
95
90
 
96
91
  def get_app():
97
92
  config_apps = typer.Typer(help="⚙️ [c] configuration subcommands", no_args_is_help=True, add_help_option=False, add_completion=False)
98
- config_apps.command("private", no_args_is_help=True, help="🔗 [v] Manage private configuration files.")(private)
99
- config_apps.command("v", no_args_is_help=True, hidden=True)(private)
100
- config_apps.command("public", no_args_is_help=True, help="🔗 [b] Manage public configuration files.")(public)
101
- config_apps.command("b", no_args_is_help=True, help="Manage public configuration files.", hidden=True)(public)
93
+ config_apps.command("private", no_args_is_help=True, help="🔗 [v] Manage private configuration files.")(create_links_export.main_private_from_parser)
94
+ config_apps.command("v", no_args_is_help=True, hidden=True)(create_links_export.main_private_from_parser)
95
+ config_apps.command("public", no_args_is_help=True, help="🔗 [b] Manage public configuration files.")(create_links_export.main_public_from_parser)
96
+ config_apps.command("b", no_args_is_help=True, help="Manage public configuration files.", hidden=True)(create_links_export.main_public_from_parser)
102
97
  config_apps.command("dotfile", no_args_is_help=True, help="🔗 [d] Manage dotfiles.")(dotfile_module.main)
103
98
  config_apps.command("d", no_args_is_help=True, hidden=True)(dotfile_module.main)
104
99
  config_apps.command("shell", no_args_is_help=False, help="🔗 [s] Configure your shell profile.")(shell)
@@ -1,24 +1,26 @@
1
1
 
2
2
  """Like yadm and dotter."""
3
3
 
4
+ from machineconfig.profile.create_links_export import ON_CONFLICT_LOOSE, ON_CONFLICT_MAPPER
4
5
  from typing import Annotated, Literal
5
6
  import typer
6
7
 
7
8
 
9
+
8
10
  def main(
9
11
  file: Annotated[str, typer.Argument(help="file/folder path.")],
10
- method: Annotated[Literal["symlink", "copy"], typer.Option(..., "--method", "-m", help="Method to use for linking files")] = "copy",
11
- on_conflict: Annotated[Literal["throw-error", "overwrite-self-managed", "backup-self-managed", "overwrite-default-path", "backup-default-path"], typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
12
- sensitivity: Annotated[Literal["private", "public"], typer.Option(..., "--sensitivity", "-s", help="Sensitivity of the config file.")] = "private",
12
+ method: Annotated[Literal["symlink", "s", "copy", "c"], typer.Option(..., "--method", "-m", help="Method to use for linking files")] = "copy",
13
+ on_conflict: Annotated[ON_CONFLICT_LOOSE, typer.Option(..., "--on-conflict", "-o", help="Action to take on conflict")] = "throw-error",
14
+ sensitivity: Annotated[Literal["private", "v", "public", "b"], typer.Option(..., "--sensitivity", "-s", help="Sensitivity of the config file.")] = "private",
13
15
  destination: Annotated[str, typer.Option("--destination", "-d", help="destination folder (override the default, use at your own risk)")] = "",) -> None:
14
16
  from rich.console import Console
15
17
  from rich.panel import Panel
16
18
  from machineconfig.utils.links import symlink_map, copy_map
17
19
  from pathlib import Path
18
20
  match sensitivity:
19
- case "private":
21
+ case "private" | "v":
20
22
  backup_root = Path.home().joinpath("dotfiles/mapper")
21
- case "public":
23
+ case "public" | "b":
22
24
  from machineconfig.utils.source_of_truth import CONFIG_ROOT
23
25
  backup_root = Path(CONFIG_ROOT).joinpath("dotfiles/mapper")
24
26
 
@@ -32,18 +34,19 @@ def main(
32
34
  dest_path.mkdir(parents=True, exist_ok=True)
33
35
  new_path = dest_path.joinpath(orig_path.name)
34
36
 
37
+
35
38
  from machineconfig.utils.path_extended import PathExtended
36
39
  match method:
37
- case "copy":
40
+ case "copy" | "c":
38
41
  try:
39
- copy_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=on_conflict)
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])
40
43
  except Exception as e:
41
44
  typer.echo(f"[red]Error:[/] {e}")
42
45
  typer.Exit(code=1)
43
46
  return
44
- case "symlink":
47
+ case "symlink" | "s":
45
48
  try:
46
- symlink_map(config_file_default_path=PathExtended(orig_path), self_managed_config_file_path=PathExtended(new_path), on_conflict=on_conflict)
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])
47
50
  except Exception as e:
48
51
  typer.echo(f"[red]Error:[/] {e}")
49
52
  typer.Exit(code=1)
@@ -8,38 +8,38 @@ in the event that username@github.com is not mentioned in the remote url.
8
8
  from pathlib import Path
9
9
  from typing import Annotated, Optional
10
10
  import typer
11
- from machineconfig.scripts.python.helpers_repos.secure_repo import main as secure_repo_main
11
+ from machineconfig.scripts.python.helpers_repos.cloud_repo_sync import main as secure_repo_main
12
12
 
13
13
 
14
14
  DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Directory containing repo(s).")]
15
15
  RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
16
- NO_UVsyncOption = Annotated[bool, typer.Option("--no-uv-sync", "-ns", help="🚫 Disable automatic uv sync after pulls.")]
16
+ UVsyncOption = Annotated[bool, typer.Option("--uv-sync/--no-uv-sync", "-u/-ns", help="Automatic uv sync after pulls.")]
17
17
  CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote.")]
18
18
 
19
19
 
20
- def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_uv_sync: NO_UVsyncOption = False) -> None:
20
+ def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
21
21
  """🚀 Push changes across repositories."""
22
22
  from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
23
- git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, auto_uv_sync=not no_uv_sync)
23
+ git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, auto_uv_sync=auto_uv_sync)
24
24
 
25
25
 
26
- def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_uv_sync: NO_UVsyncOption = False) -> None:
26
+ def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
27
27
  """⬇️ Pull changes across repositories."""
28
28
  from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
29
29
 
30
- git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, auto_uv_sync=not no_uv_sync)
30
+ git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, auto_uv_sync=auto_uv_sync)
31
31
 
32
32
 
33
- def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_uv_sync: NO_UVsyncOption = False) -> None:
33
+ def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
34
34
  """💾 Commit changes across repositories."""
35
35
  from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
36
- git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, auto_uv_sync=not no_uv_sync)
36
+ git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, auto_uv_sync=auto_uv_sync)
37
37
 
38
38
 
39
- def sync(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_uv_sync: NO_UVsyncOption = False) -> None:
39
+ def sync(directory: DirectoryArgument = None, recursive: RecursiveOption = False, auto_uv_sync: UVsyncOption = False) -> None:
40
40
  """🔄 Pull, commit, and push changes across repositories."""
41
41
  from machineconfig.scripts.python.helpers_repos.entrypoint import git_operations
42
- git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, auto_uv_sync=not no_uv_sync)
42
+ git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, auto_uv_sync=auto_uv_sync)
43
43
 
44
44
 
45
45
  def capture(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
@@ -179,3 +179,4 @@ def get_app():
179
179
  mirror_app.command(name="ctb", help="Check out to the main branch defined in the specification", hidden=True)(checkout_to_branch_command)
180
180
 
181
181
  return repos_apps
182
+
@@ -9,10 +9,8 @@ def copy_both_assets():
9
9
  create_helper.copy_assets_to_machine(which="settings")
10
10
 
11
11
 
12
- def update(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-na", help="Copy (overwrite) assets to the machine after the update")] = False):
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
13
  """🔄 UPDATE uv and machineconfig"""
14
- # from machineconfig.utils.source_of_truth import LIBRARY_ROOT
15
- # repo_root = LIBRARY_ROOT.parent.parent
16
14
  from pathlib import Path
17
15
  if Path.home().joinpath("code", "machineconfig").exists():
18
16
  shell_script = """
@@ -36,7 +34,7 @@ uv tool install --upgrade machineconfig
36
34
  else:
37
35
  from machineconfig.utils.code import run_shell_script
38
36
  run_shell_script(shell_script)
39
- if not no_copy_assets:
37
+ if copy_assets:
40
38
  copy_both_assets()
41
39
 
42
40
  def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-na", help="Copy (overwrite) assets to the machine after the update")] = False):
@@ -48,9 +46,9 @@ def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-n
48
46
  else:
49
47
  import platform
50
48
  if platform.system() == "Windows":
51
- run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.38" """)
49
+ run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=7.39" """)
52
50
  else:
53
- run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.38" """)
51
+ run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=7.39" """)
54
52
  from machineconfig.profile.create_shell_profile import create_default_shell_profile
55
53
  if not no_copy_assets:
56
54
  create_default_shell_profile() # involves copying assets too
@@ -75,7 +73,7 @@ def navigate():
75
73
  path = Path(navigator.__file__).resolve().parent.joinpath("devops_navigator.py")
76
74
  from machineconfig.utils.code import run_shell_script
77
75
  if Path.home().joinpath("code/machineconfig").exists(): executable = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with textual"""
78
- else: executable = """--with "machineconfig>=7.38,textual" """
76
+ else: executable = """--with "machineconfig>=7.39,textual" """
79
77
  run_shell_script(f"""uv run {executable} {path}""")
80
78
 
81
79