machineconfig 5.30__py3-none-any.whl → 5.31__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 (43) hide show
  1. machineconfig/scripts/python/croshell_helpers/__init__.py +0 -0
  2. machineconfig/scripts/python/devops.py +13 -122
  3. machineconfig/scripts/python/devops_helpers/cli_config.py +43 -0
  4. machineconfig/scripts/python/devops_helpers/cli_data.py +18 -0
  5. machineconfig/scripts/python/devops_helpers/cli_nw.py +39 -0
  6. machineconfig/scripts/python/{repos.py → devops_helpers/cli_repos.py} +43 -7
  7. machineconfig/scripts/python/devops_helpers/cli_self.py +41 -0
  8. machineconfig/scripts/python/devops_navigator.py +806 -0
  9. machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
  10. machineconfig/scripts/python/helpers_repos/__init__.py +0 -0
  11. machineconfig/scripts/python/{secure_repo.py → helpers_repos/secure_repo.py} +1 -1
  12. machineconfig/scripts/python/interactive.py +2 -2
  13. machineconfig/scripts/python/nw/__init__.py +0 -0
  14. machineconfig/scripts/python/sessions.py +1 -1
  15. machineconfig/scripts/python/sessions_helpers/__init__.py +0 -0
  16. machineconfig/utils/code.py +13 -3
  17. {machineconfig-5.30.dist-info → machineconfig-5.31.dist-info}/METADATA +2 -1
  18. {machineconfig-5.30.dist-info → machineconfig-5.31.dist-info}/RECORD +38 -34
  19. {machineconfig-5.30.dist-info → machineconfig-5.31.dist-info}/entry_points.txt +1 -0
  20. machineconfig/scripts/python/gh_models.py +0 -104
  21. machineconfig/scripts/python/snapshot.py +0 -25
  22. machineconfig/scripts/python/start_terminals.py +0 -121
  23. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +0 -1
  24. machineconfig/scripts/windows/wifi_conn.ps1 +0 -2
  25. /machineconfig/scripts/python/{pomodoro.py → croshell_helpers/pomodoro.py} +0 -0
  26. /machineconfig/scripts/python/{scheduler.py → croshell_helpers/scheduler.py} +0 -0
  27. /machineconfig/scripts/python/{start_slidev.py → croshell_helpers/start_slidev.py} +0 -0
  28. /machineconfig/scripts/python/{viewer.py → croshell_helpers/viewer.py} +0 -0
  29. /machineconfig/scripts/python/{viewer_template.py → croshell_helpers/viewer_template.py} +0 -0
  30. /machineconfig/scripts/{windows/select_pwsh_theme.ps1 → python/devops_helpers/choose_pwsh_theme.ps1} +0 -0
  31. /machineconfig/scripts/python/{choose_wezterm_theme.py → devops_helpers/choose_wezterm_theme.py} +0 -0
  32. /machineconfig/scripts/python/{dotfile.py → devops_helpers/cli_config_dotfile.py} +0 -0
  33. /machineconfig/scripts/python/{share_terminal.py → devops_helpers/cli_terminal.py} +0 -0
  34. /machineconfig/scripts/python/{cloud_repo_sync.py → helpers_repos/cloud_repo_sync.py} +0 -0
  35. /machineconfig/scripts/python/{mount_nfs.py → nw/mount_nfs.py} +0 -0
  36. /machineconfig/scripts/python/{mount_nw_drive.py → nw/mount_nw_drive.py} +0 -0
  37. /machineconfig/scripts/python/{mount_ssh.py → nw/mount_ssh.py} +0 -0
  38. /machineconfig/scripts/python/{onetimeshare.py → nw/onetimeshare.py} +0 -0
  39. /machineconfig/scripts/python/{wifi_conn.py → nw/wifi_conn.py} +0 -0
  40. /machineconfig/scripts/python/{wsl_windows_transfer.py → nw/wsl_windows_transfer.py} +0 -0
  41. /machineconfig/scripts/python/{sessions_multiprocess.py → sessions_helpers/sessions_multiprocess.py} +0 -0
  42. {machineconfig-5.30.dist-info → machineconfig-5.31.dist-info}/WHEEL +0 -0
  43. {machineconfig-5.30.dist-info → machineconfig-5.31.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,18 @@
1
1
  """devops with emojis"""
2
2
 
3
3
  import typer
4
- from typing import Literal, Annotated, Optional
4
+ from typing import Optional
5
5
 
6
- import machineconfig.scripts.python.repos as repos
7
- import machineconfig.scripts.python.share_terminal as share_terminal
6
+ import machineconfig.scripts.python.devops_helpers.cli_repos as cli_repos
7
+ import machineconfig.scripts.python.devops_helpers.cli_config as cli_config
8
+ from machineconfig.scripts.python.devops_helpers.cli_self import self_app
9
+ import machineconfig.scripts.python.devops_helpers.cli_data as cli_data
10
+ import machineconfig.scripts.python.devops_helpers.cli_nw as cli_network
8
11
 
9
- app = typer.Typer(help="🛠️ DevOps operations", no_args_is_help=True)
12
+
13
+ app = typer.Typer(help="🛠️ DevOps operations", no_args_is_help=True, add_completion=True)
10
14
  @app.command(no_args_is_help=True)
11
- def install( which: Optional[str] = typer.Option(None, "--which", "-w", help="Comma-separated list of program names to install."),
15
+ def install( which: Optional[str] = typer.Option(None, "--which", "-w", help="Comma-separated list of program names to install."),
12
16
  group: Optional[str] = typer.Option(None, "--group", "-g", help="Groups names. A group is bundle of apps. See available groups when running interactively."),
13
17
  interactive: bool = typer.Option(False, "--interactive", "-ia", help="Interactive selection of programs to install."),
14
18
  ) -> None:
@@ -17,126 +21,13 @@ def install( which: Optional[str] = typer.Option(None, "--which", "-w", help=
17
21
  installer_entry_point.main(which=which, group=group, interactive=interactive)
18
22
 
19
23
 
20
- app.add_typer(repos.app, name="repos", help="📁 Manage git repositories")
21
- config_apps = typer.Typer(help="⚙️ Configuration subcommands", no_args_is_help=True)
22
- app.add_typer(config_apps, name="config")
23
- app_data = typer.Typer(help="💾 Data subcommands", no_args_is_help=True)
24
- app.add_typer(app_data, name="data")
25
- nw_apps = typer.Typer(help="🔐 Network subcommands", no_args_is_help=True)
26
- nw_apps.command(name="share-terminal", help="📡 Share terminal via web browser")(share_terminal.main)
27
- app.add_typer(nw_apps, name="network")
28
- self_app = typer.Typer(help="🔄 SELF operations subcommands", no_args_is_help=True)
24
+ app.add_typer(cli_repos.app, name="repos", help="📁 Manage git repositories")
25
+ app.add_typer(cli_config.config_apps, name="config")
26
+ app.add_typer(cli_data.app_data, name="data")
29
27
  app.add_typer(self_app, name="self")
30
-
31
- @self_app.command()
32
- def update():
33
- """🔄 UPDATE essential repos"""
34
- import machineconfig.scripts.python.devops_helpers.devops_update_repos as helper
35
- helper.main()
36
- @self_app.command()
37
- def interactive():
38
- """🤖 INTERACTIVE configuration of machine."""
39
- from machineconfig.scripts.python.interactive import main
40
- main()
41
- @self_app.command()
42
- def status():
43
- """📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
44
- import machineconfig.scripts.python.devops_helpers.devops_status as helper
45
- helper.main()
46
- @self_app.command()
47
- def clone():
48
- """📋 CLONE machienconfig locally and incorporate to shell profile for faster execution and nightly updates."""
49
- import platform
50
- from machineconfig.utils.code import run_shell_script
51
- from machineconfig.profile.shell import create_default_shell_profile
52
- if platform.system() == "Windows":
53
- from machineconfig.setup_windows import MACHINECONFIG
54
- create_default_shell_profile(method="copy")
55
- else:
56
- from machineconfig.setup_linux import MACHINECONFIG
57
- create_default_shell_profile(method="reference")
58
- run_shell_script(MACHINECONFIG.read_text(encoding="utf-8"))
59
-
60
-
61
-
62
- @config_apps.command(no_args_is_help=True)
63
- def private(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for linking files"),
64
- on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
65
- which: Optional[str] = typer.Option(None, "--which", "-w", help="Specific items to process"),
66
- interactive: bool = typer.Option(False, "--interactive", "-ia", help="Run in interactive mode")):
67
- """🔗 Manage private configuration files."""
68
- import machineconfig.profile.create_frontend as create_frontend
69
- create_frontend.main_private_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
70
-
71
- @config_apps.command(no_args_is_help=True)
72
- def public(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for setting up the config file."),
73
- on_conflict: Literal["throwError", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
74
- which: Optional[str] = typer.Option(None, "--which", "-w", help="Specific items to process"),
75
- interactive: bool = typer.Option(False, "--interactive", "-ia", help="Run in interactive mode")):
76
- """🔗 Manage public configuration files."""
77
- import machineconfig.profile.create_frontend as create_frontend
78
- create_frontend.main_public_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
79
-
80
- @config_apps.command(no_args_is_help=True)
81
- def dotfile(file: Annotated[str, typer.Argument(help="file/folder path.")],
82
- overwrite: Annotated[bool, typer.Option("--overwrite", "-o", help="Overwrite.")] = False,
83
- dest: Annotated[str, typer.Option("--dest", "-d", help="destination folder")] = "",
84
- ):
85
- """🔗 Manage dotfiles."""
86
- import machineconfig.scripts.python.dotfile as dotfile_module
87
- dotfile_module.main(file=file, overwrite=overwrite, dest=dest)
88
-
89
-
90
- @config_apps.command(no_args_is_help=True)
91
- def shell(method: Annotated[Literal["copy", "reference"], typer.Argument(help="Choose the method to configure the shell profile: 'copy' copies the init script directly, 'reference' references machineconfig for dynamic updates.")]):
92
- """🔗 Configure your shell profile."""
93
- from machineconfig.profile.shell import create_default_shell_profile
94
- create_default_shell_profile(method=method)
95
-
96
-
97
- @nw_apps.command()
98
- def add_key():
99
- """🔑 SSH add pub key to this machine"""
100
- import machineconfig.scripts.python.devops_helpers.devops_add_ssh_key as helper
101
- helper.main()
102
- @nw_apps.command()
103
- def add_identity():
104
- """🗝️ SSH add identity (private key) to this machine"""
105
- import machineconfig.scripts.python.devops_helpers.devops_add_identity as helper
106
- helper.main()
107
- @nw_apps.command()
108
- def connect():
109
- """🔐 SSH use key pair to connect two machines"""
110
- raise NotImplementedError
111
-
112
- @nw_apps.command()
113
- def setup():
114
- """📡 SSH setup"""
115
- import platform
116
- if platform.system() == "Windows":
117
- from machineconfig.setup_windows import SSH_SERVER
118
- program = SSH_SERVER.read_text(encoding="utf-8")
119
- elif platform.system() == "Linux" or platform.system() == "Darwin":
120
- from machineconfig.setup_linux import SSH_SERVER
121
- program = SSH_SERVER.read_text(encoding="utf-8")
122
- else:
123
- raise NotImplementedError(f"Platform {platform.system()} is not supported.")
124
- from machineconfig.utils.code import run_shell_script
125
- run_shell_script(script=program)
126
-
127
-
128
- @app_data.command()
129
- def backup():
130
- """💾 BACKUP"""
131
- from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
132
- main_backup_retrieve(direction="BACKUP")
28
+ app.add_typer(cli_network.nw_apps, name="network")
133
29
 
134
30
 
135
- @app_data.command()
136
- def retrieve():
137
- """📥 RETRIEVE"""
138
- from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
139
- main_backup_retrieve(direction="RETRIEVE")
140
31
 
141
32
 
142
33
  # @app.command()
@@ -0,0 +1,43 @@
1
+
2
+
3
+ from typing import Literal, Annotated, Optional
4
+
5
+ import typer
6
+
7
+ config_apps = typer.Typer(help="⚙️ Configuration subcommands", no_args_is_help=True)
8
+
9
+
10
+ @config_apps.command(no_args_is_help=True)
11
+ def private(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for linking files"),
12
+ on_conflict: Literal["throwError", "overwriteSelfManaged", "backupSelfManaged", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
13
+ which: Optional[str] = typer.Option(None, "--which", "-w", help="Specific items to process"),
14
+ interactive: bool = typer.Option(False, "--interactive", "-ia", help="Run in interactive mode")):
15
+ """🔗 Manage private configuration files."""
16
+ import machineconfig.profile.create_frontend as create_frontend
17
+ create_frontend.main_private_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
18
+
19
+ @config_apps.command(no_args_is_help=True)
20
+ def public(method: Literal["symlink", "copy"] = typer.Option(..., "--method", "-m", help="Method to use for setting up the config file."),
21
+ on_conflict: Literal["throwError", "overwriteDefaultPath", "backupDefaultPath"] = typer.Option("throwError", "--on-conflict", "-o", help="Action to take on conflict"),
22
+ which: Optional[str] = typer.Option(None, "--which", "-w", help="Specific items to process"),
23
+ interactive: bool = typer.Option(False, "--interactive", "-ia", help="Run in interactive mode")):
24
+ """🔗 Manage public configuration files."""
25
+ import machineconfig.profile.create_frontend as create_frontend
26
+ create_frontend.main_public_from_parser(method=method, on_conflict=on_conflict, which=which, interactive=interactive)
27
+
28
+ @config_apps.command(no_args_is_help=True)
29
+ def dotfile(file: Annotated[str, typer.Argument(help="file/folder path.")],
30
+ overwrite: Annotated[bool, typer.Option("--overwrite", "-o", help="Overwrite.")] = False,
31
+ dest: Annotated[str, typer.Option("--dest", "-d", help="destination folder")] = "",
32
+ ):
33
+ """🔗 Manage dotfiles."""
34
+ import machineconfig.scripts.python.devops_helpers.cli_config_dotfile as dotfile_module
35
+ dotfile_module.main(file=file, overwrite=overwrite, dest=dest)
36
+
37
+
38
+ @config_apps.command(no_args_is_help=True)
39
+ def shell(method: Annotated[Literal["copy", "reference"], typer.Argument(help="Choose the method to configure the shell profile: 'copy' copies the init script directly, 'reference' references machineconfig for dynamic updates.")]):
40
+ """🔗 Configure your shell profile."""
41
+ from machineconfig.profile.shell import create_default_shell_profile
42
+ create_default_shell_profile(method=method)
43
+
@@ -0,0 +1,18 @@
1
+
2
+ import typer
3
+
4
+ app_data = typer.Typer(help="💾 Data subcommands", no_args_is_help=True)
5
+
6
+ @app_data.command()
7
+ def backup():
8
+ """💾 BACKUP"""
9
+ from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
10
+ main_backup_retrieve(direction="BACKUP")
11
+
12
+
13
+ @app_data.command()
14
+ def retrieve():
15
+ """📥 RETRIEVE"""
16
+ from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
17
+ main_backup_retrieve(direction="RETRIEVE")
18
+
@@ -0,0 +1,39 @@
1
+
2
+ import machineconfig.scripts.python.devops_helpers.cli_terminal as cli_terminal
3
+ import typer
4
+ nw_apps = typer.Typer(help="🔐 Network subcommands", no_args_is_help=True)
5
+
6
+
7
+ nw_apps.command(name="share-terminal", help="📡 Share terminal via web browser")(cli_terminal.main)
8
+
9
+
10
+ @nw_apps.command()
11
+ def add_key():
12
+ """🔑 SSH add pub key to this machine"""
13
+ import machineconfig.scripts.python.devops_helpers.devops_add_ssh_key as helper
14
+ helper.main()
15
+ @nw_apps.command()
16
+ def add_identity():
17
+ """🗝️ SSH add identity (private key) to this machine"""
18
+ import machineconfig.scripts.python.devops_helpers.devops_add_identity as helper
19
+ helper.main()
20
+ @nw_apps.command()
21
+ def connect():
22
+ """🔐 SSH use key pair to connect two machines"""
23
+ raise NotImplementedError
24
+
25
+ @nw_apps.command()
26
+ def setup():
27
+ """📡 SSH setup"""
28
+ import platform
29
+ if platform.system() == "Windows":
30
+ from machineconfig.setup_windows import SSH_SERVER
31
+ program = SSH_SERVER.read_text(encoding="utf-8")
32
+ elif platform.system() == "Linux" or platform.system() == "Darwin":
33
+ from machineconfig.setup_linux import SSH_SERVER
34
+ program = SSH_SERVER.read_text(encoding="utf-8")
35
+ else:
36
+ raise NotImplementedError(f"Platform {platform.system()} is not supported.")
37
+ from machineconfig.utils.code import run_shell_script
38
+ run_shell_script(script=program)
39
+
@@ -8,14 +8,15 @@ 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.secure_repo import main as secure_repo_main
11
+ from git import Repo, InvalidGitRepositoryError
12
+ from machineconfig.scripts.python.helpers_repos.secure_repo import main as secure_repo_main
12
13
 
13
14
 
14
- app = typer.Typer(help=" Manage development repositories", no_args_is_help=True)
15
- sync_app = typer.Typer(help=" Manage repository specifications and syncing", no_args_is_help=True)
16
- app.add_typer(sync_app, name="sync", help=" Sync repositories using saved specs")
15
+ app = typer.Typer(help="📁 Manage development repositories", no_args_is_help=True)
16
+ sync_app = typer.Typer(help="🔄 Manage repository specifications and syncing", no_args_is_help=True)
17
+ app.add_typer(sync_app, name="mirror", help="🔄 mirror repositories using saved specs")
17
18
 
18
- DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Folder containing repos or the specs JSON file to use.")]
19
+ DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Directory containing repo(s).")]
19
20
  RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
20
21
  NoSyncOption = Annotated[bool, typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls.")]
21
22
  CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote.")]
@@ -46,10 +47,9 @@ def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = Fal
46
47
 
47
48
 
48
49
  @app.command(no_args_is_help=True)
49
- def cleanup(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
50
+ def sync(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
50
51
  """🔄 Pull, commit, and push changes across repositories."""
51
52
  from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
52
-
53
53
  git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, no_sync=no_sync)
54
54
 
55
55
 
@@ -135,3 +135,39 @@ def viz(
135
135
  user_image_dir=user_image_dir, max_files=max_files, max_file_lag=max_file_lag,
136
136
  file_idle_time=file_idle_time, framerate=framerate, background_color=background_color,
137
137
  font_size=font_size, camera_mode=camera_mode)
138
+
139
+ @app.command(no_args_is_help=True)
140
+ def cleanup(repo: DirectoryArgument = None, recursive: RecursiveOption = False) -> None:
141
+ """🧹 Clean repository directories from cache files."""
142
+ if repo is None:
143
+ repo = Path.cwd().as_posix()
144
+
145
+ arg_path = Path(repo).expanduser().absolute()
146
+
147
+ if not recursive:
148
+ # Check if the directory is a git repo
149
+ try:
150
+ Repo(str(arg_path), search_parent_directories=False)
151
+ except InvalidGitRepositoryError:
152
+ typer.echo(f"❌ {arg_path} is not a git repository. Use -r flag for recursive cleanup.")
153
+ return
154
+ # Run cleanup on this repo
155
+ repos_to_clean = [arg_path]
156
+ else:
157
+ # Find all git repos recursively under the directory
158
+ git_dirs = list(arg_path.rglob('.git'))
159
+ repos_to_clean = [git_dir.parent for git_dir in git_dirs if git_dir.is_dir()]
160
+ if not repos_to_clean:
161
+ typer.echo(f"❌ No git repositories found under {arg_path}")
162
+ return
163
+
164
+ for repo_path in repos_to_clean:
165
+ typer.echo(f"🧹 Cleaning {repo_path}")
166
+ script = fr"""
167
+ cd "{repo_path}"
168
+ uv run --with cleanpy cleanpy .
169
+ # mcinit .
170
+ # find "." -type f \( -name "*.py" -o -name "*.md" -o -name "*.json" \) -not -path "*/\.*" -not -path "*/__pycache__/*" -print0 | xargs -0 sed -i 's/[[:space:]]*$//'
171
+ """
172
+ from machineconfig.utils.code import run_shell_script
173
+ run_shell_script(script)
@@ -0,0 +1,41 @@
1
+
2
+ import typer
3
+
4
+ self_app = typer.Typer(help="🔄 SELF operations subcommands", no_args_is_help=True)
5
+
6
+
7
+ @self_app.command()
8
+ def update():
9
+ """🔄 UPDATE essential repos"""
10
+ import machineconfig.scripts.python.devops_helpers.devops_update_repos as helper
11
+ helper.main()
12
+ @self_app.command()
13
+ def interactive():
14
+ """🤖 INTERACTIVE configuration of machine."""
15
+ from machineconfig.scripts.python.interactive import main
16
+ main()
17
+ @self_app.command()
18
+ def status():
19
+ """📊 STATUS of machine, shell profile, apps, symlinks, dotfiles, etc."""
20
+ import machineconfig.scripts.python.devops_helpers.devops_status as helper
21
+ helper.main()
22
+ @self_app.command()
23
+ def clone():
24
+ """📋 CLONE machienconfig locally and incorporate to shell profile for faster execution and nightly updates."""
25
+ import platform
26
+ from machineconfig.utils.code import run_shell_script
27
+ from machineconfig.profile.shell import create_default_shell_profile
28
+ if platform.system() == "Windows":
29
+ from machineconfig.setup_windows import MACHINECONFIG
30
+ create_default_shell_profile(method="copy")
31
+ else:
32
+ from machineconfig.setup_linux import MACHINECONFIG
33
+ create_default_shell_profile(method="reference")
34
+ run_shell_script(MACHINECONFIG.read_text(encoding="utf-8"))
35
+
36
+ @self_app.command()
37
+ def navigate():
38
+ """📚 NAVIGATE command structure with TUI"""
39
+ from machineconfig.scripts.python.devops_navigator import main
40
+ main()
41
+