machineconfig 7.64__py3-none-any.whl → 7.83__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.
- machineconfig/cluster/sessions_managers/utils/maker.py +4 -2
- machineconfig/jobs/installer/custom/yazi.py +120 -0
- machineconfig/jobs/installer/custom_dev/nerdfont.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +26 -12
- machineconfig/jobs/installer/custom_dev/sysabc.py +26 -5
- machineconfig/jobs/installer/installer_data.json +232 -96
- machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
- machineconfig/profile/create_helper.py +0 -12
- machineconfig/profile/create_links_export.py +2 -2
- machineconfig/profile/mapper.toml +2 -2
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/python/agents.py +22 -17
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +4 -0
- machineconfig/scripts/python/croshell.py +22 -17
- machineconfig/scripts/python/devops.py +1 -1
- machineconfig/scripts/python/devops_navigator.py +0 -4
- machineconfig/scripts/python/env_manager/env_manager_tui.py +204 -0
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +13 -13
- machineconfig/scripts/python/ftpx.py +36 -12
- machineconfig/scripts/python/helpers/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/qr_code.py +166 -0
- machineconfig/scripts/python/helpers/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
- machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
- machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
- machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
- machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
- machineconfig/scripts/python/helpers_devops/cli_config.py +10 -0
- machineconfig/scripts/python/helpers_devops/cli_nw.py +90 -10
- machineconfig/scripts/python/helpers_devops/cli_self.py +8 -7
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +7 -7
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +12 -11
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +8 -10
- machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -1
- machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +20 -9
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +2 -2
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfg.ps1 +58 -1
- machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +5 -3
- machineconfig/scripts/python/helpers_repos/count_lines.py +40 -11
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_utils/download.py +4 -3
- machineconfig/scripts/python/helpers_utils/path.py +87 -34
- machineconfig/scripts/python/interactive.py +1 -1
- machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
- machineconfig/scripts/python/msearch.py +55 -6
- machineconfig/scripts/python/nw/address.py +132 -0
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
- machineconfig/scripts/python/terminal.py +2 -2
- machineconfig/scripts/python/utils.py +12 -11
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/shells/nushell/config.nu +2 -2
- machineconfig/settings/shells/nushell/env.nu +45 -6
- machineconfig/settings/shells/nushell/init.nu +282 -95
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/settings/yazi/init.lua +4 -0
- machineconfig/settings/yazi/keymap_linux.toml +11 -4
- machineconfig/settings/yazi/theme.toml +4 -0
- machineconfig/settings/yazi/yazi_linux.toml +84 -0
- machineconfig/settings/yazi/yazi_windows.toml +58 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
- machineconfig/utils/accessories.py +7 -4
- machineconfig/utils/code.py +4 -2
- machineconfig/utils/installer_utils/github_release_bulk.py +104 -62
- machineconfig/utils/installer_utils/install_from_url.py +200 -0
- machineconfig/utils/installer_utils/installer_class.py +25 -74
- machineconfig/utils/installer_utils/installer_cli.py +40 -50
- machineconfig/utils/installer_utils/installer_helper.py +100 -0
- machineconfig/utils/installer_utils/installer_runner.py +5 -8
- machineconfig/utils/links.py +2 -2
- machineconfig/utils/meta.py +2 -2
- machineconfig/utils/options.py +3 -3
- machineconfig/utils/path_extended.py +1 -1
- machineconfig/utils/path_helper.py +0 -1
- machineconfig/utils/ssh.py +143 -409
- machineconfig/utils/ssh_utils/abc.py +8 -0
- machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
- machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
- machineconfig/utils/ssh_utils/utils.py +141 -0
- machineconfig/utils/ssh_utils/wsl.py +210 -0
- machineconfig/utils/upgrade_packages.py +2 -1
- machineconfig/utils/ve.py +11 -4
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/METADATA +2 -2
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/RECORD +96 -89
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/entry_points.txt +2 -2
- machineconfig/scripts/python/explore.py +0 -49
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfag +0 -17
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfrga +0 -21
- machineconfig/scripts/python/helpers_msearch/scripts_linux/skrg +0 -4
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfb.ps1 +0 -3
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfrga.bat +0 -20
- machineconfig/settings/yazi/yazi.toml +0 -17
- machineconfig/setup_linux/others/cli_installation.sh +0 -137
- /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
- /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/WHEEL +0 -0
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
[mgr]
|
|
3
|
+
show_hidden = true
|
|
4
|
+
ratio = [1, 3, 5]
|
|
5
|
+
|
|
6
|
+
[opener]
|
|
7
|
+
system_default = [
|
|
8
|
+
{ run = "cmd /C start \"\" \"%1\"", desc = "System Default (Windows)", for = "windows", block = false, orphan = false }
|
|
9
|
+
]
|
|
10
|
+
# Edit files in your editor (replace "code" with your editor if you like)
|
|
11
|
+
edit = [
|
|
12
|
+
{ run = "code \"%*\"", desc = "VS Code (Editor)", block = true, orphan = false, for = "windows" }
|
|
13
|
+
]
|
|
14
|
+
helix = [
|
|
15
|
+
{ run = "hx \"%*\"", desc = "Helix (Editor)", block = true, orphan = false, for = "windows" }
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Play media files
|
|
19
|
+
play = [
|
|
20
|
+
{ run = "start \"\" \"%*\"", desc = "System Default (Media)", block = false, orphan = false, for = "windows" }
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Open files/folders with system default program/explorer
|
|
24
|
+
open = [
|
|
25
|
+
{ run = "start \"\" \"%1\"", desc = "System Default (Open)", block = false, orphan = false, for = "windows" }
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
[open]
|
|
30
|
+
prepend_rules = [
|
|
31
|
+
# Text/Code files - open with VSCode by default, Helix as alternative
|
|
32
|
+
{ name = "*.py", use = ["edit", "helix"] },
|
|
33
|
+
{ name = "*.{rs,toml,yaml,yml,json,js,ts,tsx,jsx,html,css,md,txt,sh,ps1,bat,cmd}", use = ["edit", "helix"] },
|
|
34
|
+
{ mime = "text/*", use = ["edit", "helix"] },
|
|
35
|
+
]
|
|
36
|
+
rules = [
|
|
37
|
+
{ mime = "video/*", use = "play" },
|
|
38
|
+
{ mime = "application/pdf", use = ["system_default"] },
|
|
39
|
+
{ mime = "image/*", use = ["system_default"] },
|
|
40
|
+
{ name = "*", use = ["system_default"] }
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[plugin]
|
|
46
|
+
# dir = "~/AppData/Roaming/yazi/config/plugins"
|
|
47
|
+
|
|
48
|
+
[[plugin.load]]
|
|
49
|
+
name = "toggle-pane"
|
|
50
|
+
path = "toggle-pane.yazi"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
[preview]
|
|
54
|
+
# Change them to your desired values
|
|
55
|
+
max_width = 1000
|
|
56
|
+
max_height = 1000
|
|
57
|
+
|
|
58
|
+
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
. <( curl -sSL "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/uv.sh")
|
|
3
3
|
. <( curl -sSL "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/linux/wrap_mcfg")
|
|
4
4
|
|
|
5
|
-
alias devops='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
6
|
-
alias cloud='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
7
|
-
alias agents='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
8
|
-
alias sessions='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
9
|
-
alias ftpx='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
10
|
-
alias fire='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
11
|
-
alias croshell='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
12
|
-
alias utils='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
13
|
-
alias terminal='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
14
|
-
alias msearch='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.
|
|
5
|
+
alias devops='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" devops'
|
|
6
|
+
alias cloud='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" cloud'
|
|
7
|
+
alias agents='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" agents'
|
|
8
|
+
alias sessions='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" sessions'
|
|
9
|
+
alias ftpx='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" ftpx'
|
|
10
|
+
alias fire='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" fire'
|
|
11
|
+
alias croshell='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" croshell'
|
|
12
|
+
alias utils='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" utils'
|
|
13
|
+
alias terminal='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" terminal'
|
|
14
|
+
alias msearch='$HOME/.local/bin/uvx --python 3.14 --from "machineconfig>=7.83" msearch'
|
|
15
15
|
|
|
16
16
|
alias d='wrap_in_shell_script devops'
|
|
17
17
|
alias c='wrap_in_shell_script cloud'
|
|
@@ -7,4 +7,11 @@ if (-not (Test-Path -Path "$HOME\.local\bin\uv.exe")) {
|
|
|
7
7
|
Write-Output "uv binary found, updating..."
|
|
8
8
|
& "$HOME\.local\bin\uv.exe" self update
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
# `C:\Users\aalsaf01\.local\bin` is not on your PATH. To use installed Python executables, run `$env:PATH = "C:\Users\aalsaf01\.local\bin;$env:PATH"` or `uv python update-shell`.
|
|
12
|
+
& "$env:USERPROFILE\.local\bin\uv.exe" python update-shell
|
|
13
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
|
|
14
|
+
|
|
15
|
+
# & "$HOME\.local\bin\uv.exe" python install 3.14
|
|
16
|
+
uv python install 3.14
|
|
17
|
+
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
|
|
4
4
|
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
|
|
5
5
|
|
|
6
|
-
function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
7
|
-
function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
8
|
-
function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
9
|
-
function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
10
|
-
function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
11
|
-
function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
12
|
-
function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
13
|
-
function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
14
|
-
function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
15
|
-
function msearch { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.
|
|
6
|
+
function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" devops $args }
|
|
7
|
+
function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" cloud $args }
|
|
8
|
+
function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" agents $args }
|
|
9
|
+
function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" sessions $args }
|
|
10
|
+
function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" ftpx $args }
|
|
11
|
+
function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" fire $args }
|
|
12
|
+
function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" croshell $args }
|
|
13
|
+
function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" utils $args }
|
|
14
|
+
function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" terminal $args }
|
|
15
|
+
function msearch { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.83" msearch $args }
|
|
16
16
|
|
|
17
17
|
function d { wrap_in_shell_script devops @args }
|
|
18
18
|
function c { wrap_in_shell_script cloud @args }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
|
|
3
3
|
# iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
uv tool install --upgrade --python 3.14 machineconfig
|
|
5
6
|
|
|
6
7
|
devops install --group sysabc
|
|
7
8
|
|
|
@@ -12,5 +13,5 @@ devops config shell
|
|
|
12
13
|
devops config shell --which nushell
|
|
13
14
|
|
|
14
15
|
wt # start Windows Terminal to pick up config changes
|
|
15
|
-
devops install --group termabc
|
|
16
|
+
devops install --group termabc
|
|
16
17
|
devops install --group gui
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
1
|
from typing import Optional, Any
|
|
3
2
|
|
|
4
3
|
from datetime import datetime, timezone, timedelta
|
|
@@ -96,14 +95,18 @@ def human_friendly_dict(d: dict[str, Any]) -> dict[str, Any]:
|
|
|
96
95
|
return result
|
|
97
96
|
|
|
98
97
|
|
|
99
|
-
def get_repo_root(path: Path) -> Optional[Path]:
|
|
98
|
+
def get_repo_root(path: "Path") -> Optional["Path"]:
|
|
100
99
|
from git import Repo, InvalidGitRepositoryError
|
|
101
|
-
|
|
102
100
|
try:
|
|
103
|
-
repo = Repo(
|
|
101
|
+
repo = Repo(path, search_parent_directories=True)
|
|
104
102
|
root = repo.working_tree_dir
|
|
105
103
|
if root is not None:
|
|
104
|
+
from pathlib import Path
|
|
106
105
|
return Path(root)
|
|
107
106
|
except InvalidGitRepositoryError:
|
|
108
107
|
pass
|
|
109
108
|
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
from pathlib import Path
|
machineconfig/utils/code.py
CHANGED
|
@@ -38,14 +38,16 @@ def get_uv_command_executing_python_script(python_script: str, uv_with: Optional
|
|
|
38
38
|
uv_project_dir_arg = "--project" + f' "{uv_project_dir}"'
|
|
39
39
|
else:
|
|
40
40
|
uv_project_dir_arg = ""
|
|
41
|
-
print_code_string = lambda_to_python_script(lambda: print_code(code=python_script, lexer="python", desc="Temporary Python Script", subtitle="Executing via shell script"),
|
|
41
|
+
print_code_string = lambda_to_python_script(lambda: print_code(code=python_script, lexer="python", desc="Temporary Python Script", subtitle="Executing via shell script"),
|
|
42
|
+
in_global=True, import_module=False)
|
|
42
43
|
python_file.write_text(print_code_string + "\n" + python_script, encoding="utf-8")
|
|
43
44
|
shell_script = f"""uv run {uv_with_arg} {uv_project_dir_arg} {str(python_file)} """
|
|
44
45
|
return shell_script, python_file
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def run_lambda_function(lmb: Callable[[], Any], uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> None:
|
|
48
|
-
code = lambda_to_python_script(lmb,
|
|
49
|
+
code = lambda_to_python_script(lmb,
|
|
50
|
+
in_global=True, import_module=False)
|
|
49
51
|
uv_command, _py_file = get_uv_command_executing_python_script(python_script=code, uv_with=uv_with, uv_project_dir=uv_project_dir)
|
|
50
52
|
run_shell_script(uv_command)
|
|
51
53
|
def run_python_script_in_marimo(py_script: str, uv_project_with: Optional[str]):
|
|
@@ -6,12 +6,40 @@ Extracts GitHub repository URLs and fetches latest release data with rate limiti
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
import time
|
|
9
|
-
import
|
|
9
|
+
import requests
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Dict, Optional, Set
|
|
11
|
+
from typing import Any, Dict, Optional, Set, TypedDict
|
|
12
12
|
from urllib.parse import urlparse
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
class AssetInfo(TypedDict):
|
|
16
|
+
"""Type definition for GitHub release asset information."""
|
|
17
|
+
name: str
|
|
18
|
+
size: int
|
|
19
|
+
download_count: int
|
|
20
|
+
content_type: str
|
|
21
|
+
created_at: str
|
|
22
|
+
updated_at: str
|
|
23
|
+
browser_download_url: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ReleaseInfo(TypedDict):
|
|
27
|
+
"""Type definition for GitHub release information."""
|
|
28
|
+
tag_name: str
|
|
29
|
+
name: str
|
|
30
|
+
published_at: str
|
|
31
|
+
assets: list[AssetInfo]
|
|
32
|
+
assets_count: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OutputData(TypedDict):
|
|
36
|
+
"""Type definition for the output JSON data structure."""
|
|
37
|
+
generated_at: str
|
|
38
|
+
total_repositories: int
|
|
39
|
+
successful_fetches: int
|
|
40
|
+
releases: Dict[str, Optional[ReleaseInfo]]
|
|
41
|
+
|
|
42
|
+
|
|
15
43
|
def is_github_repo(url: str) -> bool:
|
|
16
44
|
"""Check if URL is a GitHub repository URL."""
|
|
17
45
|
try:
|
|
@@ -19,87 +47,99 @@ def is_github_repo(url: str) -> bool:
|
|
|
19
47
|
return parsed.netloc == "github.com" and len(parsed.path.split("/")) >= 3
|
|
20
48
|
except Exception:
|
|
21
49
|
return False
|
|
22
|
-
|
|
23
|
-
|
|
24
50
|
def extract_github_repos_from_json(json_file_path: Path) -> Set[str]:
|
|
25
51
|
"""Extract GitHub repository URLs from installer JSON file."""
|
|
26
52
|
github_repos: Set[str] = set()
|
|
27
|
-
|
|
28
53
|
try:
|
|
29
54
|
with open(json_file_path, 'r', encoding='utf-8') as file:
|
|
30
55
|
data = json.load(file)
|
|
31
|
-
|
|
32
56
|
for installer in data.get("installers", []):
|
|
33
57
|
repo_url = installer.get("repoURL", "")
|
|
34
58
|
if is_github_repo(repo_url):
|
|
35
59
|
github_repos.add(repo_url)
|
|
36
|
-
|
|
37
60
|
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
38
61
|
print(f"Error reading {json_file_path}: {e}")
|
|
39
|
-
|
|
40
62
|
return github_repos
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def get_repo_name_from_url(repo_url: str) -> str:
|
|
44
|
-
"""Extract owner/repo from GitHub URL."""
|
|
63
|
+
def get_repo_name_from_url(repo_url: str) -> Optional[tuple[str, str]]:
|
|
64
|
+
"""Extract owner/repo from GitHub URL as a tuple (username, repo_name)."""
|
|
45
65
|
try:
|
|
46
66
|
parsed = urlparse(repo_url)
|
|
47
67
|
path_parts = parsed.path.strip("/").split("/")
|
|
48
|
-
return
|
|
68
|
+
return (path_parts[0], path_parts[1])
|
|
49
69
|
except (IndexError, AttributeError):
|
|
50
|
-
return
|
|
70
|
+
return None
|
|
51
71
|
|
|
52
72
|
|
|
53
|
-
def fetch_github_release_data(
|
|
54
|
-
|
|
73
|
+
def fetch_github_release_data(
|
|
74
|
+
username: str,
|
|
75
|
+
repo_name: str,
|
|
76
|
+
version: Optional[str] = None,
|
|
77
|
+
) -> Optional[Dict[str, Any]]:
|
|
78
|
+
"""Fetch GitHub release data for the latest or a specific tag."""
|
|
79
|
+
|
|
55
80
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
f"https://api.github.com/repos/{repo_name}/releases/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
timeout=30
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
if result.returncode != 0:
|
|
69
|
-
print(f"❌ Failed to fetch data for {repo_name}: {result.stderr}")
|
|
81
|
+
requested_version = (version or "").strip()
|
|
82
|
+
if requested_version and requested_version.lower() != "latest":
|
|
83
|
+
url = f"https://api.github.com/repos/{username}/{repo_name}/releases/tags/{requested_version}"
|
|
84
|
+
else:
|
|
85
|
+
url = f"https://api.github.com/repos/{username}/{repo_name}/releases/latest"
|
|
86
|
+
|
|
87
|
+
response = requests.get(url, timeout=30)
|
|
88
|
+
if response.status_code != 200:
|
|
89
|
+
print(f"❌ Failed to fetch data for {username}/{repo_name}: HTTP {response.status_code}")
|
|
70
90
|
return None
|
|
71
|
-
|
|
72
|
-
response_data = json
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
print(f"🚫 Rate limit exceeded for {repo_name}")
|
|
91
|
+
|
|
92
|
+
response_data = response.json()
|
|
93
|
+
message = response_data.get("message")
|
|
94
|
+
if isinstance(message, str):
|
|
95
|
+
if "API rate limit exceeded" in message:
|
|
96
|
+
print(f"🚫 Rate limit exceeded for {username}/{repo_name}")
|
|
78
97
|
return None
|
|
79
|
-
|
|
80
|
-
print(f"🔍 No releases found for {repo_name}")
|
|
98
|
+
if "Not Found" in message:
|
|
99
|
+
print(f"🔍 No releases found for {username}/{repo_name}")
|
|
81
100
|
return None
|
|
82
|
-
|
|
101
|
+
|
|
83
102
|
return response_data
|
|
84
|
-
|
|
85
|
-
except (
|
|
86
|
-
print(f"❌ Error fetching {repo_name}: {
|
|
103
|
+
|
|
104
|
+
except (requests.RequestException, requests.Timeout, json.JSONDecodeError) as error:
|
|
105
|
+
print(f"❌ Error fetching {username}/{repo_name}: {error}")
|
|
87
106
|
return None
|
|
88
107
|
|
|
89
108
|
|
|
90
|
-
def
|
|
109
|
+
def get_release_info(
|
|
110
|
+
username: str,
|
|
111
|
+
repo_name: str,
|
|
112
|
+
version: Optional[str] = None,
|
|
113
|
+
) -> Optional[ReleaseInfo]:
|
|
114
|
+
"""Return sanitized release information for the requested repository."""
|
|
115
|
+
release_data = fetch_github_release_data(username, repo_name, version)
|
|
116
|
+
if not release_data:
|
|
117
|
+
return None
|
|
118
|
+
return extract_release_info(release_data)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def extract_release_info(release_data: Dict[str, Any]) -> Optional[ReleaseInfo]:
|
|
91
122
|
"""Extract relevant information from GitHub release data."""
|
|
92
123
|
if not release_data:
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
return None
|
|
125
|
+
assets: list[AssetInfo] = []
|
|
126
|
+
for asset in release_data.get("assets", []):
|
|
127
|
+
asset_info: AssetInfo = {
|
|
128
|
+
"name": asset.get("name", ""),
|
|
129
|
+
"size": asset.get("size", 0),
|
|
130
|
+
"download_count": asset.get("download_count", 0),
|
|
131
|
+
"content_type": asset.get("content_type", ""),
|
|
132
|
+
"created_at": asset.get("created_at", ""),
|
|
133
|
+
"updated_at": asset.get("updated_at", ""),
|
|
134
|
+
"browser_download_url": asset.get("browser_download_url", "")
|
|
135
|
+
}
|
|
136
|
+
assets.append(asset_info)
|
|
97
137
|
return {
|
|
98
138
|
"tag_name": release_data.get("tag_name", ""),
|
|
99
139
|
"name": release_data.get("name", ""),
|
|
100
140
|
"published_at": release_data.get("published_at", ""),
|
|
101
|
-
"assets":
|
|
102
|
-
"assets_count": len(
|
|
141
|
+
"assets": assets,
|
|
142
|
+
"assets_count": len(assets)
|
|
103
143
|
}
|
|
104
144
|
|
|
105
145
|
|
|
@@ -132,7 +172,7 @@ def main() -> None:
|
|
|
132
172
|
return
|
|
133
173
|
|
|
134
174
|
# Fetch release data with rate limiting
|
|
135
|
-
release_mapping: Dict[str,
|
|
175
|
+
release_mapping: Dict[str, Optional[ReleaseInfo]] = {}
|
|
136
176
|
total_repos = len(all_github_repos)
|
|
137
177
|
|
|
138
178
|
print(f"\n🚀 Fetching release data for {total_repos} repositories...")
|
|
@@ -140,24 +180,26 @@ def main() -> None:
|
|
|
140
180
|
print("-" * 60)
|
|
141
181
|
|
|
142
182
|
for i, repo_url in enumerate(sorted(all_github_repos), 1):
|
|
143
|
-
|
|
183
|
+
repo_info = get_repo_name_from_url(repo_url)
|
|
144
184
|
|
|
145
|
-
if not
|
|
185
|
+
if not repo_info:
|
|
146
186
|
print(f"⚠️ [{i:3d}/{total_repos}] Invalid repo URL: {repo_url}")
|
|
147
187
|
continue
|
|
148
|
-
|
|
149
|
-
print(f"📡 [{i:3d}/{total_repos}] Fetching: {repo_name}", end=" ... ")
|
|
150
188
|
|
|
151
|
-
|
|
189
|
+
username, repo_name = repo_info
|
|
190
|
+
repo_full_name = f"{username}/{repo_name}"
|
|
191
|
+
|
|
192
|
+
print(f"📡 [{i:3d}/{total_repos}] Fetching: {repo_full_name}", end=" ... ")
|
|
152
193
|
|
|
153
|
-
|
|
154
|
-
|
|
194
|
+
release_info = get_release_info(username, repo_name)
|
|
195
|
+
|
|
196
|
+
if release_info:
|
|
155
197
|
release_mapping[repo_url] = release_info
|
|
156
|
-
assets_count = release_info
|
|
157
|
-
tag = release_info
|
|
198
|
+
assets_count = release_info["assets_count"]
|
|
199
|
+
tag = release_info["tag_name"]
|
|
158
200
|
print(f"✅ {tag} ({assets_count} assets)")
|
|
159
201
|
else:
|
|
160
|
-
release_mapping[repo_url] =
|
|
202
|
+
release_mapping[repo_url] = None
|
|
161
203
|
print("❌ No data")
|
|
162
204
|
|
|
163
205
|
# Rate limiting - wait 5 seconds between requests (except for the last one)
|
|
@@ -165,7 +207,7 @@ def main() -> None:
|
|
|
165
207
|
time.sleep(5)
|
|
166
208
|
|
|
167
209
|
# Save results
|
|
168
|
-
output_data = {
|
|
210
|
+
output_data: OutputData = {
|
|
169
211
|
"generated_at": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
|
|
170
212
|
"total_repositories": len(all_github_repos),
|
|
171
213
|
"successful_fetches": len([v for v in release_mapping.values() if v]),
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from machineconfig.utils.installer_utils.installer_helper import install_deb_package
|
|
10
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows
|
|
11
|
+
from machineconfig.utils.installer_utils.github_release_bulk import (
|
|
12
|
+
get_repo_name_from_url,
|
|
13
|
+
fetch_github_release_data,
|
|
14
|
+
extract_release_info,
|
|
15
|
+
AssetInfo,
|
|
16
|
+
)
|
|
17
|
+
from machineconfig.utils.path_extended import DECOMPRESS_SUPPORTED_FORMATS, PathExtended
|
|
18
|
+
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
|
|
23
|
+
SUPPORTED_GITHUB_HOSTS = {"github.com", "www.github.com"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _format_size(size_bytes: int) -> str:
|
|
27
|
+
if size_bytes <= 0:
|
|
28
|
+
return "0 B"
|
|
29
|
+
units = ("B", "KiB", "MiB", "GiB", "TiB")
|
|
30
|
+
value = float(size_bytes)
|
|
31
|
+
index = 0
|
|
32
|
+
while value >= 1024 and index < len(units) - 1:
|
|
33
|
+
value /= 1024
|
|
34
|
+
index += 1
|
|
35
|
+
return f"{value:.1f} {units[index]}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _derive_tool_name(repo_name: str, asset_name: str) -> str:
|
|
39
|
+
repo_segment = repo_name.split("/", maxsplit=1)[-1]
|
|
40
|
+
repo_clean = repo_segment.replace(".git", "").lower()
|
|
41
|
+
repo_filtered = "".join(char for char in repo_clean if char.isalnum())
|
|
42
|
+
if repo_filtered:
|
|
43
|
+
return repo_filtered
|
|
44
|
+
asset_clean = asset_name.lower()
|
|
45
|
+
asset_filtered = "".join(char for char in asset_clean if char.isalnum())
|
|
46
|
+
if asset_filtered:
|
|
47
|
+
return asset_filtered
|
|
48
|
+
return "githubapp"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _download_and_prepare(download_url: str) -> PathExtended:
|
|
52
|
+
archive_path = PathExtended(download_url).download(folder=INSTALL_TMP_DIR)
|
|
53
|
+
extracted_path = archive_path
|
|
54
|
+
if extracted_path.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
55
|
+
extracted_path = archive_path.decompress()
|
|
56
|
+
archive_path.delete(sure=True)
|
|
57
|
+
if extracted_path.is_dir():
|
|
58
|
+
nested_items = list(extracted_path.glob("*"))
|
|
59
|
+
if len(nested_items) == 1:
|
|
60
|
+
nested_path = PathExtended(nested_items[0])
|
|
61
|
+
if nested_path.suffix in DECOMPRESS_SUPPORTED_FORMATS:
|
|
62
|
+
extracted_path = nested_path.decompress()
|
|
63
|
+
nested_path.delete(sure=True)
|
|
64
|
+
return extracted_path
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _finalize_install(repo_name: str, asset_name: str, version: str, extracted_path: PathExtended, console: "Console") -> None:
|
|
68
|
+
from rich.panel import Panel
|
|
69
|
+
|
|
70
|
+
if extracted_path.suffix == ".deb":
|
|
71
|
+
install_deb_package(extracted_path)
|
|
72
|
+
tool_name_deb = _derive_tool_name(repo_name, asset_name)
|
|
73
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name_deb).parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name_deb).write_text(version, encoding="utf-8")
|
|
75
|
+
console.print(Panel(f"Installed Debian package for [green]{tool_name_deb}[/green]", title="✅ Complete", border_style="green"))
|
|
76
|
+
return
|
|
77
|
+
system_name = platform.system()
|
|
78
|
+
tool_name = _derive_tool_name(repo_name, asset_name)
|
|
79
|
+
rename_target = f"{tool_name}.exe" if system_name == "Windows" else tool_name
|
|
80
|
+
try:
|
|
81
|
+
if system_name == "Windows":
|
|
82
|
+
installed_path = find_move_delete_windows(downloaded_file_path=extracted_path, exe_name=tool_name, delete=True, rename_to=rename_target)
|
|
83
|
+
elif system_name in {"Linux", "Darwin"}:
|
|
84
|
+
installed_path = find_move_delete_linux(downloaded=extracted_path, tool_name=tool_name, delete=True, rename_to=rename_target)
|
|
85
|
+
else:
|
|
86
|
+
console.print(Panel(f"Unsupported operating system: {system_name}", title="❌ Error", border_style="red"))
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
except IndexError:
|
|
89
|
+
if system_name == "Windows":
|
|
90
|
+
installed_path = find_move_delete_windows(downloaded_file_path=extracted_path, exe_name=None, delete=True, rename_to=rename_target)
|
|
91
|
+
elif system_name in {"Linux", "Darwin"}:
|
|
92
|
+
installed_path = find_move_delete_linux(downloaded=extracted_path, tool_name="", delete=True, rename_to=rename_target)
|
|
93
|
+
else:
|
|
94
|
+
raise
|
|
95
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name).parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
INSTALL_VERSION_ROOT.joinpath(tool_name).write_text(version, encoding="utf-8")
|
|
97
|
+
console.print(Panel(f"Installed [green]{tool_name}[/green] to {installed_path}\nVersion: {version}", title="✅ Complete", border_style="green"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def install_from_github_url(github_url: str) -> None:
|
|
101
|
+
from machineconfig.utils.options import choose_from_options
|
|
102
|
+
from rich.console import Console
|
|
103
|
+
from rich.panel import Panel
|
|
104
|
+
|
|
105
|
+
console = Console()
|
|
106
|
+
repo_info = get_repo_name_from_url(github_url)
|
|
107
|
+
if repo_info is None:
|
|
108
|
+
console.print(Panel(f"Invalid GitHub URL: {github_url}", title="❌ Error", border_style="red"))
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
owner, repo = repo_info
|
|
111
|
+
repo_name = f"{owner}/{repo}"
|
|
112
|
+
console.print(Panel(f"Fetching latest release for [green]{repo_name}[/green]", title="🌐 GitHub", border_style="blue"))
|
|
113
|
+
release_raw = fetch_github_release_data(owner, repo)
|
|
114
|
+
if not release_raw:
|
|
115
|
+
console.print(Panel("No releases available for this repository.", title="❌ Error", border_style="red"))
|
|
116
|
+
raise typer.Exit(1)
|
|
117
|
+
|
|
118
|
+
release_info = extract_release_info(release_raw)
|
|
119
|
+
if not release_info:
|
|
120
|
+
console.print(Panel("Failed to parse release information.", title="❌ Error", border_style="red"))
|
|
121
|
+
raise typer.Exit(1)
|
|
122
|
+
|
|
123
|
+
assets = release_info["assets"]
|
|
124
|
+
if not assets:
|
|
125
|
+
console.print(Panel("No downloadable assets found in the latest release.", title="❌ Error", border_style="red"))
|
|
126
|
+
raise typer.Exit(1)
|
|
127
|
+
binary_assets = assets
|
|
128
|
+
selection_pool = binary_assets if binary_assets else assets
|
|
129
|
+
if not selection_pool:
|
|
130
|
+
console.print(Panel("No assets available for installation.", title="❌ Error", border_style="red"))
|
|
131
|
+
raise typer.Exit(1)
|
|
132
|
+
|
|
133
|
+
# First pass: collect all formatted data and calculate column widths
|
|
134
|
+
asset_data = []
|
|
135
|
+
for asset in selection_pool:
|
|
136
|
+
name = asset["name"]
|
|
137
|
+
download_url = asset["browser_download_url"]
|
|
138
|
+
if name == "" or download_url == "":
|
|
139
|
+
continue
|
|
140
|
+
size = asset["size"]
|
|
141
|
+
download_count = asset.get("download_count", 0)
|
|
142
|
+
created_at = asset.get("created_at", "")
|
|
143
|
+
|
|
144
|
+
# Format each field
|
|
145
|
+
size_str = f"[{_format_size(size)}]"
|
|
146
|
+
downloads_str = f"{download_count:,}"
|
|
147
|
+
date_str = created_at.split("T")[0] if created_at else "N/A"
|
|
148
|
+
|
|
149
|
+
asset_data.append({
|
|
150
|
+
"name": name,
|
|
151
|
+
"size_str": size_str,
|
|
152
|
+
"downloads_str": downloads_str,
|
|
153
|
+
"date_str": date_str,
|
|
154
|
+
"asset": asset
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
# Calculate maximum widths for alignment
|
|
158
|
+
max_name_len = max(len(item["name"]) for item in asset_data) if asset_data else 0
|
|
159
|
+
max_size_len = max(len(item["size_str"]) for item in asset_data) if asset_data else 0
|
|
160
|
+
max_downloads_len = max(len(item["downloads_str"]) for item in asset_data) if asset_data else 0
|
|
161
|
+
|
|
162
|
+
# Second pass: build aligned labels
|
|
163
|
+
options_map: dict[str, AssetInfo] = {}
|
|
164
|
+
for item in asset_data:
|
|
165
|
+
name_padded = item["name"].ljust(max_name_len)
|
|
166
|
+
size_padded = item["size_str"].ljust(max_size_len)
|
|
167
|
+
downloads_padded = item["downloads_str"].rjust(max_downloads_len)
|
|
168
|
+
|
|
169
|
+
label = f"{name_padded} {size_padded} | ⬇ {downloads_padded} | 📅 {item['date_str']}"
|
|
170
|
+
options_map[label] = item["asset"]
|
|
171
|
+
|
|
172
|
+
if not options_map:
|
|
173
|
+
console.print(Panel("Release assets lack download URLs.", title="❌ Error", border_style="red"))
|
|
174
|
+
raise typer.Exit(1)
|
|
175
|
+
selection_label = choose_from_options(options=list(options_map.keys()), msg="Select a release asset", multi=False, header="📦 GitHub Release Assets", fzf=True)
|
|
176
|
+
selected_asset = options_map[selection_label]
|
|
177
|
+
download_url_value = selected_asset["browser_download_url"]
|
|
178
|
+
asset_name_value = selected_asset["name"]
|
|
179
|
+
if download_url_value == "":
|
|
180
|
+
console.print(Panel("Selected asset lacks a download URL.", title="❌ Error", border_style="red"))
|
|
181
|
+
raise typer.Exit(1)
|
|
182
|
+
asset_name = asset_name_value if asset_name_value != "" else "github_binary"
|
|
183
|
+
version = release_info["tag_name"] if release_info["tag_name"] != "" else "latest"
|
|
184
|
+
console.print(Panel(f"Downloading [cyan]{asset_name}[/cyan]", title="⬇️ Download", border_style="magenta"))
|
|
185
|
+
extracted_path = _download_and_prepare(download_url_value)
|
|
186
|
+
_finalize_install(repo_name=repo_name, asset_name=asset_name, version=version, extracted_path=extracted_path, console=console)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def install_from_binary_url(binary_url: str) -> None:
|
|
190
|
+
from rich.console import Console
|
|
191
|
+
from rich.panel import Panel
|
|
192
|
+
|
|
193
|
+
console = Console()
|
|
194
|
+
parsed = urlparse(binary_url)
|
|
195
|
+
asset_candidate = parsed.path.split("/")[-1] if parsed.path else ""
|
|
196
|
+
asset_name = asset_candidate if asset_candidate != "" else "binary_asset"
|
|
197
|
+
host = parsed.netloc if parsed.netloc != "" else "remote host"
|
|
198
|
+
console.print(Panel(f"Downloading [cyan]{asset_name}[/cyan] from [green]{host}[/green]", title="⬇️ Download", border_style="magenta"))
|
|
199
|
+
extracted_path = _download_and_prepare(binary_url)
|
|
200
|
+
_finalize_install(repo_name="", asset_name=asset_name, version="latest", extracted_path=extracted_path, console=console)
|