From 9968233dee422e6110306db784a66d7d4ff62d17 Mon Sep 17 00:00:00 2001 From: b-tsammmons <233864410+b-tsammons@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:32:28 -1000 Subject: [PATCH] Making enhancements to make building and packaging the application possible --- .gitignore | 72 ++----------- BUILD.md | 6 +- GitHub_Pulse/requirements.txt | 9 -- README.md | 13 ++- SETUP.md | 9 +- pyproject.toml.example | 46 ++++++++ .../app_components/__init__.py | 0 .../app_components/ai_manager.py | 99 +----------------- .../app_components/assets/flow-diagram.png | Bin .../assets/github_pulse_img.png | Bin .../assets/pulse_logo_gray_no_bkg.png | Bin .../assets/pulse_logo_white_no_bkg.png | Bin .../assets/pulse_logo_white_no_bkg_github.png | Bin .../assets/pulse_logo_white_w_black_bkg.png | Bin .../app_components/cache_manager.py | 0 .../app_components/config_manager.py | 0 .../app_components/github_api.py | 0 .../app_components/main_gui.py | 0 .../app_components/processing_log_dialog.py | 0 .../app_components/settings_dialog.py | 12 ++- .../app_components/settings_manager.py | 0 {GitHub_Pulse => src}/app_components/utils.py | 0 .../app_components/workflow.py | 0 {GitHub_Pulse => src/assets}/icon.png | Bin src/assets/splash_android.png | Bin 0 -> 25961 bytes src/config.json | 14 +++ {GitHub_Pulse => src}/main.py | 0 src/requirements.txt | 13 +++ 28 files changed, 110 insertions(+), 183 deletions(-) delete mode 100644 GitHub_Pulse/requirements.txt create mode 100644 pyproject.toml.example rename {GitHub_Pulse => src}/app_components/__init__.py (100%) rename {GitHub_Pulse => src}/app_components/ai_manager.py (97%) rename {GitHub_Pulse => src}/app_components/assets/flow-diagram.png (100%) rename {GitHub_Pulse => src}/app_components/assets/github_pulse_img.png (100%) rename {GitHub_Pulse => src}/app_components/assets/pulse_logo_gray_no_bkg.png (100%) rename {GitHub_Pulse => src}/app_components/assets/pulse_logo_white_no_bkg.png (100%) rename {GitHub_Pulse => src}/app_components/assets/pulse_logo_white_no_bkg_github.png (100%) rename {GitHub_Pulse => src}/app_components/assets/pulse_logo_white_w_black_bkg.png (100%) rename {GitHub_Pulse => src}/app_components/cache_manager.py (100%) rename {GitHub_Pulse => src}/app_components/config_manager.py (100%) rename {GitHub_Pulse => src}/app_components/github_api.py (100%) rename {GitHub_Pulse => src}/app_components/main_gui.py (100%) rename {GitHub_Pulse => src}/app_components/processing_log_dialog.py (100%) rename {GitHub_Pulse => src}/app_components/settings_dialog.py (99%) rename {GitHub_Pulse => src}/app_components/settings_manager.py (100%) rename {GitHub_Pulse => src}/app_components/utils.py (100%) rename {GitHub_Pulse => src}/app_components/workflow.py (100%) rename {GitHub_Pulse => src/assets}/icon.png (100%) create mode 100644 src/assets/splash_android.png create mode 100644 src/config.json rename {GitHub_Pulse => src}/main.py (100%) create mode 100644 src/requirements.txt diff --git a/.gitignore b/.gitignore index 9dc87cf..7635f14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Byte-compiled / optimized / DLL files __pycache__/ -*.py[codz] +*.py[cod] *$py.class # C extensions @@ -46,7 +46,7 @@ htmlcov/ nosetests.xml coverage.xml *.cover -*.py.cover +*.py,cover .hypothesis/ .pytest_cache/ cover/ @@ -94,35 +94,20 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml +*.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control #pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ @@ -136,7 +121,6 @@ celerybeat.pid # Environments .env -.envrc .venv env/ venv/ @@ -175,45 +159,9 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ - -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc - -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files -.cursorignore -.cursorindexingignore - -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ - -# AI Files -.claude/ - -# Settings files -config.json +# Flet +storage/ # Build build/ - -# FVM Version Cache -.fvm/ \ No newline at end of file +pyproject.toml \ No newline at end of file diff --git a/BUILD.md b/BUILD.md index 66701ad..a3205d5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -15,7 +15,7 @@ ```bash git clone https://github.com/TySP-Dev/github_pulse.git -cd github_pulse/GitHub_Pulse +cd github_pulse/src ``` ## Python Virtual Environment @@ -59,9 +59,11 @@ export PATH="$PATH":"$HOME/.pub-cache/bin" ```bash # Set ICU data file path (Windows example) -$env:FLUTTER_ICU_DATA_FILE="C:\Users\micro\fvm\versions\3.29.0\bin\cache\artifacts\engine\windows-x64\icudtl.dat" +$env:FLUTTER_ICU_DATA_FILE="C:\path\to\flutter\bin\cache\artifacts\engine\windows-x64\icudtl.dat" ``` +# Example path $env:FLUTTER_ICU_DATA_FILE="C:\Users\\flutter\bin\cache\artifacts\engine\windows-x64\icudtl.dat" + ```bash # Install Flutter version 3.29.0 fvm install 3.29.0 diff --git a/GitHub_Pulse/requirements.txt b/GitHub_Pulse/requirements.txt deleted file mode 100644 index 4f71249..0000000 --- a/GitHub_Pulse/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Core dependencies -requests>=2.31.0 -keyring>=24.0.0 # Secure credential storage - -# UI Framework -flet>=0.28.0 - -# Git operations (required for AI functionality) -GitPython>=3.1.40 diff --git a/README.md b/README.md index a9e7155..c83f877 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![GitHub Pulse Logo](GitHub_Pulse/app_components/assets/pulse_logo_white_no_bkg_github.png) +# ![GitHub Pulse Logo](src/app_components/assets/pulse_logo_white_no_bkg_github.png) A Python-based GUI application for GitHub automation workflows and AI assisted workflows. @@ -6,19 +6,22 @@ A Python-based GUI application for GitHub automation workflows and AI assisted w > This project is currently in active development. Features and functionality may change frequently. Bug reports and contributions are welcome and encouraged! > Please be aware that some features may be incomplete or unstable. -![GitHub Pulse img](GitHub_Pulse/app_components/assets/github_pulse_img.png) +![GitHub Pulse img](src/app_components/assets/github_pulse_img.png) ## Pulse Workflow -![Pulse Workflow img](GitHub_Pulse/app_components/assets/flow-diagram.png) +![Pulse Workflow img](src/app_components/assets/flow-diagram.png) ## Project Structure ```text github_pulse/ -├── GitHub_Pulse/ # Main application directory +├── src/ # Main application directory │ ├── app.py # Application entry point │ ├── requirements.txt # Python dependencies +│ ├── assets # Images for build +│ │ ├── icon.png # Application icon +│ │ └── splash_android.png # Splash screen image │ └── app_components/ # Application modules │ ├── assets/ # Images and assets │ │ ├── flow-diagram.png # Workflow diagram @@ -55,7 +58,7 @@ github_pulse/ 1. **Clone the repository** ```bash git clone https://github.com/TySP-Dev/github_pulse.git - cd github_pulse/GitHub_Pulse + cd github_pulse/src ``` 2. **Create and activate virtual environment** diff --git a/SETUP.md b/SETUP.md index d1e1aa4..c00f338 100644 --- a/SETUP.md +++ b/SETUP.md @@ -18,7 +18,7 @@ A Python-based GUI application for GitHub automation workflows. ```bash git clone https://github.com/TySP-Dev/github_pulse.git - cd github_pulse/GitHub_Pulse + cd github_pulse/src ``` 2. **Create Virtual Environment** (Recommended) @@ -44,7 +44,7 @@ A Python-based GUI application for GitHub automation workflows. 4. **Run the Application** ```bash - python app.py + python main.py ``` > [!NOTE] @@ -79,9 +79,12 @@ The project is organized as follows: ```text github_pulse/ -├── GitHub_Pulse/ # Main application directory +├── src/ # Main application directory │ ├── app.py # Application entry point │ ├── requirements.txt # Python dependencies +│ ├── assets # Images for build +│ │ ├── icon.png # Application icon +│ │ └── splash_android.png # Splash screen image │ └── app_components/ # Application modules │ ├── assets/ # Images and assets │ │ ├── flow-diagram.png # Workflow diagram diff --git a/pyproject.toml.example b/pyproject.toml.example new file mode 100644 index 0000000..51cdabc --- /dev/null +++ b/pyproject.toml.example @@ -0,0 +1,46 @@ +[project] +name = "GitHub Pulse" +version = "0.0.1" +description = "A Python-based GUI application for GitHub automation workflows and AI assisted workflows." +readme = "README.md" +requires-python = ">=3.9" +authors = [ + { name = "Flet developer", email = "you@example.com" } +] +dependencies = [ + "flet==0.28.3", + "requests>=2.32.5", + "keyring>=25.6.0", + "GitPython>=3.1.45", + "openai>=2.8.0", + "anthropic>=0.72.1" +] + +[tool.flet] +# org name in reverse domain name notation, e.g. "com.mycompany". +# Combined with project.name to build bundle ID for iOS and Android apps +org = "com.mycompany" + +# project display name that is used as an app title on Android and iOS home screens, +# shown in window titles and about app dialogs on desktop. +product = "GitHub Pulse" + +# company name to display in about app dialogs +company = "Flet" + +# copyright text to display in about app dialogs +copyright = "Copyright (C) 2025 by Flet" + +[tool.flet.app] +path = "src" + +[tool.uv] +dev-dependencies = [ + "flet[all]==0.28.3", +] + +[tool.poetry] +package-mode = false + +[tool.poetry.group.dev.dependencies] +flet = {extras = ["all"], version = "0.28.3"} \ No newline at end of file diff --git a/GitHub_Pulse/app_components/__init__.py b/src/app_components/__init__.py similarity index 100% rename from GitHub_Pulse/app_components/__init__.py rename to src/app_components/__init__.py diff --git a/GitHub_Pulse/app_components/ai_manager.py b/src/app_components/ai_manager.py similarity index 97% rename from GitHub_Pulse/app_components/ai_manager.py rename to src/app_components/ai_manager.py index cedeac4..0c1d71f 100644 --- a/GitHub_Pulse/app_components/ai_manager.py +++ b/src/app_components/ai_manager.py @@ -10,10 +10,8 @@ import subprocess import sys import tempfile import time -import tkinter as tk from abc import ABC, abstractmethod from pathlib import Path -from tkinter import messagebox from typing import List, Tuple, Optional @@ -2761,10 +2759,6 @@ def get_detailed_python_environment_info() -> dict: def install_ai_packages_enhanced(packages: List[str], parent_window=None) -> bool: """Enhanced AI provider package installation with better error handling - - Args: - packages: List of package names to install - parent_window: Parent tkinter window for dialog (optional) Returns: bool: True if installation successful or user declined, False if failed @@ -2801,102 +2795,11 @@ def install_ai_packages_enhanced(packages: List[str], parent_window=None) -> boo f"{venv_info}\n\n" f"Would you like to install them now?\n\n" f"This will run: pip install {' '.join(packages)}") - - # Show confirmation dialog - try: - import tkinter as tk - from tkinter import messagebox - - # If we have a parent window, use it; otherwise create a temporary root - if parent_window: - result = messagebox.askyesno("Install AI Packages", message, parent=parent_window) - else: - # Create temporary root window for the dialog - temp_root = tk.Tk() - temp_root.withdraw() # Hide the temporary window - result = messagebox.askyesno("Install AI Packages", message) - temp_root.destroy() - - if not result: - print("User declined to install AI packages") - return True # User declined, but this isn't a failure - - except Exception as e: - print(f"Could not show dialog, proceeding with installation: {e}") - # If dialog fails, ask in console - response = input(f"Install AI packages ({package_list})? [y/N]: ").lower() - if response not in ['y', 'yes']: - return True - - # Install packages - try: - if in_venv: - print(f"Installing packages to virtual environment: {package_list}") - else: - print(f"Installing packages system-wide: {package_list}") - - for package in packages: - print(f"Installing {package}...") - - # Build pip command - pip_cmd = [sys.executable, '-m', 'pip', 'install', package] - - # First attempt: Direct installation - result = subprocess.run(pip_cmd, capture_output=True, text=True, timeout=300) - - # If direct install fails and we're not in venv, try with --user flag - if result.returncode != 0 and not in_venv: - print(f" Direct installation failed, trying with --user flag...") - pip_cmd_user = [sys.executable, '-m', 'pip', 'install', '--user', package] - result = subprocess.run(pip_cmd_user, capture_output=True, text=True, timeout=300) - - if result.returncode == 0: - print(f"✅ Successfully installed {package} (user-local)") - continue - - if result.returncode != 0: - print(f"❌ Failed to install {package}:") - print(f"Error: {result.stderr}") - - # Show more helpful error message - if "permission" in result.stderr.lower() or "access" in result.stderr.lower(): - print(" This appears to be a permissions issue.") - if not in_venv: - print(" Consider:") - print(" 1. Running as administrator") - print(" 2. Using a virtual environment") - print(" 3. Installing with --user flag") - - return False - else: - install_type = "to virtual environment" if in_venv else "system-wide" - print(f"✅ Successfully installed {package} ({install_type})") - - success_msg = "✅ AI packages installed successfully!" - if in_venv: - success_msg += f" (installed to virtual environment)" - else: - success_msg += f" (installed system-wide)" - - print(success_msg) - print("Please restart the application to use the new AI features.") - return True - - except subprocess.TimeoutExpired: - print("❌ Installation timed out") - return False - except Exception as e: - print(f"❌ Error installing packages: {e}") - return False def validate_ai_provider_setup(config: dict, parent_window=None) -> bool: """Validate AI provider setup and offer to install missing modules - - Args: - config: Configuration dictionary - parent_window: Parent tkinter window for dialogs - + Returns: bool: True if setup is valid or user handled the issue """ diff --git a/GitHub_Pulse/app_components/assets/flow-diagram.png b/src/app_components/assets/flow-diagram.png similarity index 100% rename from GitHub_Pulse/app_components/assets/flow-diagram.png rename to src/app_components/assets/flow-diagram.png diff --git a/GitHub_Pulse/app_components/assets/github_pulse_img.png b/src/app_components/assets/github_pulse_img.png similarity index 100% rename from GitHub_Pulse/app_components/assets/github_pulse_img.png rename to src/app_components/assets/github_pulse_img.png diff --git a/GitHub_Pulse/app_components/assets/pulse_logo_gray_no_bkg.png b/src/app_components/assets/pulse_logo_gray_no_bkg.png similarity index 100% rename from GitHub_Pulse/app_components/assets/pulse_logo_gray_no_bkg.png rename to src/app_components/assets/pulse_logo_gray_no_bkg.png diff --git a/GitHub_Pulse/app_components/assets/pulse_logo_white_no_bkg.png b/src/app_components/assets/pulse_logo_white_no_bkg.png similarity index 100% rename from GitHub_Pulse/app_components/assets/pulse_logo_white_no_bkg.png rename to src/app_components/assets/pulse_logo_white_no_bkg.png diff --git a/GitHub_Pulse/app_components/assets/pulse_logo_white_no_bkg_github.png b/src/app_components/assets/pulse_logo_white_no_bkg_github.png similarity index 100% rename from GitHub_Pulse/app_components/assets/pulse_logo_white_no_bkg_github.png rename to src/app_components/assets/pulse_logo_white_no_bkg_github.png diff --git a/GitHub_Pulse/app_components/assets/pulse_logo_white_w_black_bkg.png b/src/app_components/assets/pulse_logo_white_w_black_bkg.png similarity index 100% rename from GitHub_Pulse/app_components/assets/pulse_logo_white_w_black_bkg.png rename to src/app_components/assets/pulse_logo_white_w_black_bkg.png diff --git a/GitHub_Pulse/app_components/cache_manager.py b/src/app_components/cache_manager.py similarity index 100% rename from GitHub_Pulse/app_components/cache_manager.py rename to src/app_components/cache_manager.py diff --git a/GitHub_Pulse/app_components/config_manager.py b/src/app_components/config_manager.py similarity index 100% rename from GitHub_Pulse/app_components/config_manager.py rename to src/app_components/config_manager.py diff --git a/GitHub_Pulse/app_components/github_api.py b/src/app_components/github_api.py similarity index 100% rename from GitHub_Pulse/app_components/github_api.py rename to src/app_components/github_api.py diff --git a/GitHub_Pulse/app_components/main_gui.py b/src/app_components/main_gui.py similarity index 100% rename from GitHub_Pulse/app_components/main_gui.py rename to src/app_components/main_gui.py diff --git a/GitHub_Pulse/app_components/processing_log_dialog.py b/src/app_components/processing_log_dialog.py similarity index 100% rename from GitHub_Pulse/app_components/processing_log_dialog.py rename to src/app_components/processing_log_dialog.py diff --git a/GitHub_Pulse/app_components/settings_dialog.py b/src/app_components/settings_dialog.py similarity index 99% rename from GitHub_Pulse/app_components/settings_dialog.py rename to src/app_components/settings_dialog.py index c8f1d59..d63718b 100644 --- a/GitHub_Pulse/app_components/settings_dialog.py +++ b/src/app_components/settings_dialog.py @@ -44,14 +44,18 @@ class SettingsDialog: dialog = self._create_dialog() print(f"Dialog created: {dialog}") - # IMPORTANT: Set the reference before opening - if self.dialog_ref.current is None: - print("dialog_ref.current is None, setting it now") - self.dialog_ref.current = dialog + # Always set the dialog ref to the current dialog instance + print("Setting dialog_ref.current to new dialog instance") + self.dialog_ref.current = dialog # Use Flet 0.28+ API: page.open() instead of page.dialog print("Opening dialog with page.open()...") self.page.open(dialog) + # Ensure UI updates immediately (useful when console is not visible) + try: + self.page.update() + except Exception: + pass print("page.open() completed") # Start async initialization diff --git a/GitHub_Pulse/app_components/settings_manager.py b/src/app_components/settings_manager.py similarity index 100% rename from GitHub_Pulse/app_components/settings_manager.py rename to src/app_components/settings_manager.py diff --git a/GitHub_Pulse/app_components/utils.py b/src/app_components/utils.py similarity index 100% rename from GitHub_Pulse/app_components/utils.py rename to src/app_components/utils.py diff --git a/GitHub_Pulse/app_components/workflow.py b/src/app_components/workflow.py similarity index 100% rename from GitHub_Pulse/app_components/workflow.py rename to src/app_components/workflow.py diff --git a/GitHub_Pulse/icon.png b/src/assets/icon.png similarity index 100% rename from GitHub_Pulse/icon.png rename to src/assets/icon.png diff --git a/src/assets/splash_android.png b/src/assets/splash_android.png new file mode 100644 index 0000000000000000000000000000000000000000..6159727486ebd2d374db0aab7f9e2fadfa0c9a8d GIT binary patch literal 25961 zcmeFYWmFu_vM)+-C%C)&4DJMX_dxK$-5r9wce-uVTNg`>aOZvRabWxQL4%^Xvjp!P*6~4a(P)u zNKUePu24|ueQ%$4$;{})P*AX&)|$F*x=M-yW{&o3CgzT&AT}?1CrEE7C?QcVClfPU zkQ;?5$kN(DnEtf2lb*ubT$o;mTM3}#BmuIrmi2J~Y4|8>n)%q8@tf0&iXaPl2|yUw zgWOCgyzK29Tm`&@>Hpy?0Qvn^%}x*L?=Fy%Fuj$Vo09-LyQilonflNZ;ebr>4-ZL@tC@?nlbf}p1H~Ip6H`Zb zH(`2uWQu>Y7ISlRu{Qk=cL!HClYeU5-$>XXLbIEgyRrk>0B_Txpb+|3e*rOb*SF%o z2GIaH|5Nx^$9M$2eJn_xVG0sP;H z^KTA9?Ej7V{|8~5{u`vBm9p#|HlNpOq~83-XYxt{=uoEi>9Naorr{qgNF$PgF48;9OMFWVWcp3 zG!vE-li{+_vsRPxbfj>11-S@oiOHzin@UTl@p4g^x|lea3yZ(iXsSuL=x|ZEf$W{^ zAjlUMw}bc(3P>}BI>?+t+|kaQLeks8#NOJB0s=QjOBWL-E68tEN06I~H-&_qyD5dD zqotz|`~Nzt|E(ter5uPCf%p^l|9Blp;Xi&0B7`u#3&bng7rH+|K^e2jNs4KDWgV?3 zIhgAZ4sO2c8#T1rj~u_qLL<`tj10=)x1&Tk;3pmmBK0C&4Cd`l0K(AF6~pBH(MD9x z?~DCZ;Y-k@hM0hfg=;7}`CU_eHEDuy<>61(uV!4RjaS2CA1J7ZTZoDXyrk8XjI<<; zb6wQR=M-?LFKTmS77CS(TpqiNEc&LWT3XhxUXuhZD@`?D=ZKJA3Qd@>)wF!vInKD~ z9lLlYx|d6mAzUo1k>B+$WGTI%8a|c2NIiBB;3jYOYZ^8ZeQDxp%|UVUU37_{Pv6%< zuVlWp*$EhTN!y_r78vd>_ER0M1@8JL8^k4UYo362crW$Hj8yKo@4ZFV&sl=>l_BG@ zvDfGf%Z*RXJ@O1Q^yeLsZbv-S=47Vrv|NF zQ1|`x@X5$Xn%CQ@VtOnwFyBFtBiq^ITYCD&M(${MI>EF&c2OnW@ny%0#r5iP=IHs< z63VGJMX+(HHWT;GIZ5-PDXCFJWbwr6!M$)$L2|BO7Pqh5`DhD z9<^J8Nvt#0Z++Jy+iFjkYIJEbk4%g>R~FN=%`Dr4+w22%k3THHp_UoLaBRTQ)Kd<@ zNDNe<-f4_LqY?nBU@%zp(1;T#iNmK8?2V0MDL#n%de*1jT1}#Q78PZ#wnghypOm*z zZ9PBs63gK&+Hu4v)wB-ZBG8nkC1VmM4oB|$SG;1Q`Rp|BF0bb<{nbd#KLqoOecDhw zGEsDX{i+GA_)e*t%L(?z$rh2x!Y$V@<9Jt z#C|6@>6r~LWsbDiL0nQ9?ySacsuT(>0;g!Q7wO>z9Z>3=Aqzqb$HP z!Ln!fP=0^aL>I=2l01>%I!N44n$uc&L8nvEQ2pXH*Ckgz#5pO$yn>NrTn>d~3Ke9@ z#5kmfe19cNgXO&>8A9gG#_x+)Z}DDw&Uh^p>6Z(l!YJ9wuRouKzk>$yw7v!@9vF@R00HmS0OEAnWege8BI zHS-^W*Ne^qnQqxF1(CWk5W#4db(rBL0@_DL#EUA<$e2jN4HTE7jr32Sv@U||sS{;) zqTavh)VyATcC^IIjxXO@Iyr&(8yzaBMAZ0_2>=Q=8AIV=H>Y|f+R_mI3)a! zD2xqy#dK|3!4{9@7!rCPv3L=Kff(44iIVUnU%aOKvQ(-@A|Di-*W0h7-yTGd=2wY*-$rp=aun((R)nb-t(hgSj+-oA4j5+h!i9&5>1ROe7Z2l_0DZ9V7RgViHVEY#Vd@@%qZJJcw_z+^wOyZnqO$$VhB84|BBI^D6A%JBsHg0US1sjei=eXT1p?6Y1Hw~+tmQO{^ zSz%~HTvP?Ijv);yqN^cSDfI*eJLE!I)ci2#aEj}tjZI`{1faOo6HCwgDBctenU5v8 zylYb?cnEJx5*7KT;Ijx~O+V!s+SzgH?*IZUGTh+rW6u>xlg|nk%Q8MC*Jawo!`JW!!+<*TwWhx#Orp@PkPUUp}_z z|NIT6r=H_x1I@vAozRK&Zpv8W%n>#)i_b&))7tX`a)|OE8PCD4YwzFEq!SKnHEKB7 z@SXLK6o+oA#mr!c*72sl%jBRKo-%y;J2MhdKE!9819J8I0(iNq>jrPY^^{-s7*m)@ zo^6HXBD301i%a>|ueduni8)D~#YM4#FN0liw}0t(f+qX?u9Rg?vh54~m1n?uI+XN9 zf0=gD=__}S%Gs-~JqF3xG9gx%gZQV&A8L}*)^=Ybc>cCLiCi>b&*Sm;($YQN7j`Oy z!T4{@_9VZ5PpEJJcFnwBpAVDQU*vop(gZ(sYNeky*Sk1=@+QW~`Hau*LVuJ4lSx;) zYj@QEPK;uO!wC2wt5XmPcml$F<7=7t^ZZeYhd1-GKRIBx^C3oIW@cuwSICW2G{EAu ztNC~O(^A_ZX8=~)c{tPSy1_}(?CrCeSG(LjJM|%cS*nT!er@U^WMSW+E6MI| zMd4nSLYF&`p6KK6I#x4QId+D9ITKMU0@li4X zmA|ogk)v!ucBae)ZHU>Pgp}8)aLW~=bTH^b8GhBJ3SGwdEly)ty)S}w|K^(BSXpEe zZI$7bKDBn?c$Pj=Ck|PHA-0}4B;(aCp>CcQ#V`5U8QCoSt!|P9f=8-E--K~@NJRI} zjD|0UFIJ>uS5cni1TiH^DsoCvD%Y%}Vr=A%J+GYnMTGx(|Bh-ETSc*l=H=c zFN(is?9QtbGE%RkN%|xPIrJ+8ukRzwkAcVgRA)LEtQ3%NuOvG(S+xO^;AgN6U!WQd1 z`$o+e)}F98X7(>#h~ISoGH7TP`gY7$6hF$PQZD!S<9?3U3P+mNeMn+1rhyQBbDN>4 zXkw;guM_%dUgi9mDn8w6N>tP>o?P^DQuH|Fj*H0l)VY@1(Adx1ysb&446_`X z^S2V}q|(pU*OYUPyoI>)xoGA!15j{m8%Zg4yNbAG`wbv|oe>g4fFBd=S;{SvwrsLJ z&dVzJ#4q&eFJndV1(WG-p;VwD(<_%k*2G<%cj^2S#8iu}wG-(jQFEno^wzvW+&n|1 z#8@#6Rlfj>&V1?81QeYc%+Y1ARie|NZ<;k2EJCZUT)bFuk*Fz6WZi8~-r;V2!-RO8 z<0xdfFk|fj)5X$;Gi!2vNJPT-=8g0kKK>xHf#VBvE>5MbFjGw5V)jxy1+DH)DsVd7HLR!FT{<|aLHEdXw|phTbazEWh7R~QMV%F#g3pOHO3?b@Q!Wn$R+QhsTLhx$a>HVCU82J@ zM%$CA#?5CjkVp849Dbu3W;33#{>52p%U=wQ66KLkSAQPYOH9g%WH6`O3Gs4ucktWR z(kVSBoeQU-D^lgh9^P@ju)B{RH@UV@^50#Gt{8*r^W*Wu`4vq0?GadA+mP)jT6T>N z;~$$KE@Yb?5&>{3EYaQe;1B3s&LXvMv4XvgAF3Wlg-g1Ha)s?N2#i-~LQe+4LD=x= zV#If*lqoiF`8U_gaBlIjY7S`K_~%(k4k|K_{85}?`^*<(1&rf;PWJ*Dx3{yPD{CA$ zcXAsPo>5Vj_h4_$NQgs0cndY@?!T1HwhHg`*AZ&*XE21W(E11kvo#YYEmSA)2F42p z13WKAO}oJcXK>=V1{!jNNPG$xO_vAl9htp%L<5W)mG6vT4ZH8;|1`7XZ}lN6<7n-@ z?EHOhKUN#4qt(nVa>#Soyep{kit4XT9NG_hi;ZXoDavS@xJ+oAr-g*0Nb;-RQ>ly$ z_U(jeDechR^y()tURiq*My#TJ0rYu;Z%Jmc;_ttPt)YF)p4et(yR6a7^*Y8imOD#* z&rYRm{1n|cPs&0U#|a-R|0R7+Yt?1&CFdyJ%C zeALpg&YS6C=RWMWC_8B^)eRD*$ znHZkZbepgKk;;pk zZ6MC@3v-L6nrQ#<4YYVUL{?i@=u67|7b2u6g%ZP%kuUGlqWUJeu%aM-kW+50DD!u^ z`v4NsmmxHwP^ZTxkP@I-Ohp6Kyr2LUpqkJ2s4w>eFSpw4Hce9#O!S@#R7>{6$`j#F z$A`>m(Pq&O&!@}xClJADX|)P#&N5i|EGT1-8<=+?l^$}6bu?6q{W3&c_bzj@D%D|Z zXgG6qZQ@-={TWtP*3`IGW9qIotnauwmlApPc`-Ky0|B3y+^+^ZUXtv?oBN8J0kkBl zyLTTrB(v()TBHfJE*!tuxO%j1J?9cIAmN_O%hT4oZ)o1le4>1!3?IpQOo`&`%0SLo z4LvlDuLqoX=dG)(RCeTAOq&-jm?VC+pj(3Hmq?NhBO{aMzA2JMm8K{BA#LjuA%vMs zMLXK4hjrCRLTs+67n>{k*&b)%uVYOrXx3H5_nOz;c46?@H!vbss~9~3)u89-g`%XM z_zUhkAC+H!j8E-2g;@?Tq$@sFD%1QVk0N=CDke!BLoy?*F^aIK@sCDjL$CLO zmxE=j7tnE;x`2o?#pDp@2QLrWj0uylZHp~E7Vtf617;CcQ=aNJz%5UCgj;es^cE>h z`pRjRm2#8=?~^=CKGSrCw)NIfpijvQr;)FF&8D~B3DK$D#c4v7j?$k>N3FOlA);hA zYL&MLY?7d+fRt&?$`~gMcIkHon1);Z{-?y z^}XS@bX%nJsPQ~uT?qX$ z^)C3<+gN9yESNt;sjr2kXhb!J(`-g^GRE67jdx8Z>*RzUyU`OjXBO;8(E?wKUpd!- z1-TKBgG8`9>3JkUzlOl8eX0Bx0L5K`0Y7g}a{Owo@iRpNm()RZ>dTGYbWiEyGm9h}IXBfIW;!N6pz3SIoDaX6zl9pvcnj1z#Jk zBkaRKv8ano&KNx;CJuQq0Q^2ZaT|+|@e62|uqS`g8^SQ!;UxUEdis2~zL{VCt-r8> zqY54u%ot|MZM0TJS(m1TXy_A+j_Klg5q7p$0=Sozq=8jE{^zuEUmFIrslj+ zTs^-UnJ-J8yg8&iG&6ALEx8OXxKJ@^@V`sK?g`~nNvoRY;Hx-KSUxKayKukH9+w@l zFYTg});_tdgF;F_aDO~m_%SFrVnDoY8Pw6Om?D~eNO9qZh4#Q1b99Y9Y%M$anf#{s zd1)P+i}2+yNj)*~kK?Z;qGOAX#2l6?h03EZJ51LPB*~6}GB9_>rV>1328(MTXuYBk zcY!@Y(&pAMy%UkMM!}T8M?sqgsh>d*3}RAub5Zp!z*vl??vvz^wR<%m<;Y==x7!?wHI6*>(YqrhXC#UI)K(rujp?3AzNe8i$`_kq2V5&d22N@92|? zo;^Z;eN{t67Hw9$p`yi6-uv)V)Tw%u@}e&i{+STuj@vj>;9(}arffYq87fAV*{BQK zkU20d?#7(AO57Ak+r+S$Ma_=PbY1ELt&kP{9KexHGs09NJs15N&e;JN2BLE6G{S3u zhnV8@*t0UH{BGbY9!5LGYSYVVgcWZ8Y8~mUt3KbhEHrjXtSLJma+(*Z`Iy1f<4;w| zupV-j&KKiL_$5}^ZDn$_On9xw7*%aJ^AChSz3-?Q(eVWylMKRr0y`45Q4ml`!$PcS z7O^arF(g0obv|aCrhuTKS}-PAW#Bp%8NX84yrqPa6W2P=aEqXxxPMMw>bZ~6HJsey z_lV2<(akFGlyw}%Hn{`|Ae$9#o#($l)JA_TCNK}9jiY_JW+VD|(cw!px7=a;c_X=V ztjPWKSFS!r+3vP#9zy%)ZJB+26HZ_XCsq)p+#bMudtZ?fnRW~;UNf8J0y~k&uc2LL z)0*+{?4#AXw?nMesV~QE!y7~T=%o~HMB}%XI0=c?MO}vi?s=^yjyZ@d>SD5{m18M0 zNruZ%i>DMxFW1zDYLk9ky^IEW{S6$PzRt6N(<1z|Ka8@utIK6(6_-QU8+g{p_Po7V z;zt#d1JYLL`}H2a#F8Ri$)*<`M#oW1VJd6g;>9Of%#`b6l_kSb1lvjbL?xcC}TQ>YB*M;5u-2wxc zLK=H%+E$gmQD=m)x0{vfdV;O;T-^(8SvT}6cGYVsXK z{Tf4%kRoK&Np1cws#BeR_ej@_g7Dy&#%%(hF5PDIhPhf5r5FEa#?z)S-LY7uR84>I8G|u_2+q5BQ1$^E~a{ifznaO4omuQYhqYKJd6GOU|lD$kjxcNVW zKbn|Z%P&U}ONnKNN>bAJ{)#v+kQw8Hw{>cYzho~&D9WPW>tIRAsN*3S!J@ueVW%4;T{L3^KwL~4PC0dK&QM%bg z3~fk00jt|Q75%}l7}UWG$;r^vYEVwGpQz0d&iEsSJsQmHs$cp$+91Hc1QzpKVI zvZ~9A!2+kNNwmU{OhF9rtK+HNWjS#EI4bayxPWXVG&Q|n1kCsSjp#nqfx=5W`kZ#T zIQol4$YH}Wat=MYHsRZ{<%5NEJG4HT1&W$8l8oPgVN%r(V=HNkC)y}hg}dM=$pP=a z;1C=$wXNa^B^YLLaz~f6v`vOu_z%$Z?W&XtGf18ZxY{~m(PqDe6gUb&v*QW3b>}#1 zKtNbGLl~b;-2EA|AQs0q9O2A|QLv4*K_=|!k+If;B#m+yvJKm&Ec7Sxt4-I9)tYs` zKV74TyBjIQSj)(w%XD}b0?YcIjm`e*LftUqgetx$^EYC>N2q(4F_9QfsBL`G#Yf5` z2mS3RsL7A3YF53=TnoKv9Mw=)3Z#$CgI;VHeGyUKGsycsPTmJdlRt^UZFd@$V?IsB zs*ExFFC=Pr->Z!@4g!W~*%E-pn@^kiUKADH1q!ph7O0}o<(cM`QDPFL?V)0++l9;j1O?OMsR1Q{a@tJU6gr)J z*Ck13mi=|>)jM<%K5>!^f^%xf^{Hb>C)C^~;-)O&^i9=)B!<}t{TOqx4L^xxo8>%7 z`2JV<$?;I{V!nLa@EtvufrO3vCX+q77Q%Pt&2!o3wCqfgXrxrPd;~4lgs&BSS|N$w z6TO(A)`_ zdD-M3{qsumFf{X(u#d&l;e>hvtICmbX%neOfVxkL@KKq|T~_!>B{)PMcH7NS(m2E^ zzTm9eJF{(4#jdc9BTj)%*nTjD(K zficS2@tTpO4)GjIOVLfsW(p9OB~+|AR(0wJllIXKqB%;Gh4E^!lJH7i#Z@ZL7Cjjy&Bwcg z5n$_y$Ms$v2QGK$C?BOlRdSz&<$c50g8IQ9cGN<|xf6ESKM%vG*D$3e^!$s>H7DIC z0YvX$z?p^yv|c|_nX0sDRuZP!h3ZQeCPQxrG^yBpWc~^zQ(11Pvw2aoazxjS)R9-S znQ1dpa}r@S^ZU4J^v8T5bEMUp6nyDIE}c-T)OnhxUv2oPHrWej`D5>{E5>^Nep_9r z$a)~d`-atEMj!S(V;0Pmg9e7{5jQ{RXN9$}OII_m$_5kc#GTCiii{NU&JzoS*ZtJ* z9Q=2hux_TnouLL(U@hq#P78@}a4G&9k2?!5QX#0{xM^XpT%}$U5o1-drt&+ndbd2$WKG#$!>A^ao6^VsG0c+r zor-u8jRC=x^}a7#i?YXTmhv%mgDh0plwFJ`o0q9o=tUcds9mIy zA6rhqDScQ2&*s7ru+&NNX(6-fdqa!IY_+59VOI3T7R_2S<~1tJO0(DoREmibTab*} zUwo#yP^JFH*fPzLK&v5fpAViN8&2NWP5NGt=uG7!Ea2J6wS^|~nQ7mKc7CG~2_1}q z5qW8<>7)q#DthHK>WyUt*n-bL?Y!=KIvbQb!-L9>yMPgwrUpFUc6tYFbqh?CK!v{! zdM?7u1m1oN1XO=daAhi$5Fy#Q3&VX9x$D;Y)h3;RA#2cGm9yoO_bY2)A8!6vaGR4N zEgThy>u5VVH~^ii=-gHRif!IW^iBpZEH+2%4f$TAPtC5yt>7|fIeB4()DPqM;GoP2 zAPaLSFRbeXUz9WUBsk3GIHw!`hpLioUQ~6#l#46s+1{DdRAEk}{MPMfd#I>#)KTS7 zl$uOeP=Mz;g#_2QS~5XX!mB5U#$?fW(0D$k6mo|=(0ZI}k^AZaB!OQJMZSVJbz2~L z7^Zw_p&l0YRyQHv^uI%Gg+1ljS0?j0YL5VPTKV+SPLW4?EYIv4_BW+VG}OSEBx2yY z^huC3hr#K>Rf{ivQPS=EvFl758Z6$_8gWlSEhGUBt6_UbBB<@OCc*4%qNb(&QFJ`G z6lYbL(rLr8_zGW!XWs{;RVNqD zwgVl*kt4Fw+;it>S(*Fi-^bPvSdmS`!iS1n;g9R;Fht8ct+?f}sZh(gBVvTKm47Gu z8s?0x;j_;QMv0^i`%RrJrTa+0*xgEGY9N_=HW{p=hj4>Sg_??l?63^m%PD2mJvu&= zJtKK6V;0a7TzJEfgi22i@BNj3v$VTV{M|?SJH2|PYM?OzFsT??$u$3c#se0R3tYXL!G#2_nazR2j9s5LLM()e?C5&1iqRU7~b|j>ik7nGqV zIBzK5G?vq zE-skq+%H}c*sN`K>yu3KHQw=8S)fj=A}V%K)MbXng&}CJ0T{6_pKkHNNukP8?6OVa z0AKWu)^7#h6g2n9<}^-Xm}5)lvLVmkepC~s!}`O-;z|wUHOlGlLKWmWZ|}2i{8%wf zT!BshDVJ@4Z_B5r9=F=2`;ijEPW_ii=Q85&vs$`kXYe2(982Hg3%N#@Tbt@_%EH%t zXghtY)Cb%iziVLxtyN5n>Kcwf6z4kB^KV_%+1JgjO*XQoN~Mt7$cQM@%xXK+PiF{W z)cu_DU^v1?#jFHf_E?_@bKK{gjq@9%+GQgUNx_L`+?R1r%_3=2ZqKg zwfdvio6bQ0mz<&j?srN(fUHix1^@9XX(Jr+Hn0`nt+BZE~gh7A&%Vj>EfObCf1+{~KF z$?V`jl6DOI)}}sJxN2a zjNM>L;I_aPb3}>c5Jy>`kOR%d-PmM2h?LdJo7XSESjB@L&ZZ2vS7wO<$^qkeLa;^j*pdqzG-*W zV&g^Qagx`DlaoZAFuxbAK6EF|EqeGy%_`p3u8o!Ev!!`KnqeLpO{YHcye}VW`VexH zN6C0FpZge)x1)ukP?limaeRg`@V@vTu>o(gnhM{KI5J4$KUnuT@aE4err+ie4WOa) zyC0I|HLi>vIV)wxNK$!5)_PN`fTdSYjqMN%fh`dwE|oMy1S?$!0ucxm!>)2Uw_^)E z)P$)xd$F$_eGwK0QU-1b$RY`MoR?WDB!cj~ZO+5|Yyu11gWpEZa|9(j9w=aB!uRP+ zHpUQ(y{lz(%_Cb83Te7@pir}-Xj9L&_V74eUHKBm9cDZLr(o?(`cs+&*)a|GsnQ+` z+t2OYA|X`U@X{)|`d!3rDv@?VEJb#aZ7kv|YD4Yh8j+DxY-cz}L(Z#``eNG~su_3I zrCYJQd7PM0LJn`qJ;3N5^^J^;!LA0^FDMChME!ViWCL18XAYaqA4?6cY2XP?)15=6 zr0f6mwO$$?=oUN|gKzP>f>D~UQ1%XXdn~nn34x5sya|}xa7jwf z#_-QOu%m>Kw!(0|O=n9xj0g@(^2>r_A~-b1^D^-bSUG;<&Q`POcB^1-i~qgvt?P;C z6**PkwfH;4VWtzuympQ+cYrBs#K#88c~F^!Jh&03%&JXZIyYS;jtfR(w~0kddP1xnN7leHg5HZrR}zAv zXD_F7UN!DmGrb(=9VRBG!Sc79Z7&u$NL@NSgpGgvydENsvTfUyeC`@nokLV{08A?0 zlqE5s#v$ADh-H#^crhtmDar`w)CLu;nYzO2 zd{rNsaU8Fz*2s^@Ul$q)Sjy)W?nk>`EF&QFy^=k+X+bT+^+0g^=Mvn7Jc1q1C~ZPdxCB(6WgO zk(V zF`3;PaL$5kW8{fzvWn++f8t60lb6?8ofOAB_Pcp1Oe;K!h@v2IR(a6b(JAwwF(P~!oH0B7&NGS=Yx~^uT&0nAq)%~di(Ka zPN2ZkhA()DF`f6lZgfmucuq%@cr`0Z zN|LOq864dZ zxC!j7!&@S0Ve^hVFN(juHjq-e14ll&TU%ZL+Y)8XIsS2CW9aCOUJX8Rf}||uVI`nu z*|7vLKRBJ^o57|-h#se`l8H}xgg;3;@@!k;$Eg`djgbar`3$RB^6GlGleM3UM%kdo zt#iBwxW_K|GcvJ7hxi0M0!RGt%&w`3x~?2ogWHYTET2Hi(987`RZZq2yl_q+s~NNG zq_QD-;;!~EcA?>^Vj&A8Nt$kZI)X-$Y7z)Z&ShKukwZ(O#H5IyW8`#pNf0JvIjUA# zglKQeZ_+dj7sAb6k+B``*X)`HNC+NH-3vxV3eP$b;==jag(>Po_4+; zJZ?AC|N2N?{X8x8JE~SrJ2!2w@tGJCNxU&g1e&6&K{{i1fC-hp9&)n-;@;gs*x*2+0v&vz(z z(S|8}CA)(OIaXE0CIV>F>Zg1IG{iYP5R;G1gu098<&m_5?r9j&zB)$(TAyf*t2NzQ z_4BxKCbV$q#y@fA?@Ja8Ejq*XKFWMhWTR&kdX$@LR6$(26y@3u7TZV z4pzc$q}k(;ag;eC1=r2$4~6imsp*36E=YKr1(|X=t`n&7vdq4yKHWl3>5BT2N0>_H zeO0?})*Md=%XtVElnST9IbCHCogWLs83^a~SWQ@yp~g!bs)fYsi9^(Y3MAF;;`hyP z7>x=9R9~>%wg%@`aE&|sj?yTF=-bn}t2g>loru)F{xsdTHZ<#R&H~Ui><9ZScSJp} zOqcN zo%`Kb#dI*tqust|D}Pv6O%&zfd=QqMLK(3zyIOEe#KsBoDrq9!zQu1g;0*rqK`O6H zRz6>309ybR{=-ZRk7{l3-+&OYL%XqQnH2g@Kd*R?s zLgHq!Dfp!VXAx>X8`I%Fya6QUO1UtpF20o<7+)`kp;|$nzflX5*T#t{r=4=0nXYBU zu9!_0XBFbs_nq3Rp{+r8oKc4afkFFi-Gb@8^WfUBd)95z6c;#{LK-Jm?QGSa-e#MWD0**(&Lm}O|^RG#e}z( zzi+IOXFcFl_}xPwbcZ@|Kl&}LQO3MVN?M$9^IQ{HvB3yPK@#UML(;_ruYln~5Trjd zytjANgry%fZIKGr@g3|%{b`X3WJ^Ccxi?@hadIyk-^+Hodj|O1elEuozrkUJBi64apNozDPAN zq2&0f9%`{LAduZE{9te=R7AOOwS{LoY+Uk0GS~~csQKw&#B_+=r-Z;772p^&AmK=r zxF7Y81m9;8%O=$zr_M{UZ|zSrEZtrhNbu;vm^M(49O_ZgQMBDSrczFIS7VlAXusg{ndSHBfu2Cs zXSJ&ErbW^z-@`@%#a~!=zxA~t+r`niQf*G*5PvvUX4JW|`sQF)y?C3b;OOcjG)~V*FJ2Q7zz-I%5D%Ks~lAB%?pR7zaN}h{`d< zmlkrUmCqnJ3~RC*U`9^<`^gdq3oZDw>j%2Y_k%c8+-4uUeeDVD%t*vMcUPnU2Ku2S z7Y$;|-#?p$2aorXR{#0tMi5j1NfXdiK|j}L);)#)X_1Wrc};(v_ZL69rB)bXQEc|b z9YV`MW(;?Jw{dqjXz#0FVRt`e>cKqzrqNQ@pVQz+@Jr({!SD)_+d}=8T>LGlKGcKTr1^~HT@h^qO}GV%e4Fgh zH5(wMi2GwCTesCM>rnsikrkKGjiJx0`$35P=ES=j5-}Mn4xz-}}LcwM&#x-(h&l-6{GX!~->r)_R;lQM8u2iB7#ST%^u~=9LEh6RcNzvwi$!jGB};W-j+jvu@Q@$0$_)BQQ!@h;pIcDyZ$1{lk16)7I|w*@c#z)=>Ts(__+j_!#N zJD*}}`&kZy(V}kU>FZHh?{K5%DcVa`8gE>;8FwgJ2X zzi5cbD6{yx_*?f#E%i!|m1W4+7Z}2g{Jk??MvX{4Hchjy*f!#yL{3iDjv?2$3ih@2jQMXVmS2z$?$w-DktQI$r;G+Mo5;AG2d$AC_NT$BEO3EUn`V8B0={ z%U1%wG4c}3MH9l>TI`3NPp99}^U_`Lc6g}29(VfOac|hx20kJLJ{>+k8S!uJPu|Xw zW+cbzh^~8{X+GO-qy`*qi^5j0TQV56K^BYMs59Vlt!5DOSq5dKwz8Fdz%dUd@v3Ztqs0V6p z&im+^wWdre6ZtxqLchI<1(t-n_(p26GfZ_#><=GEenvvL zo(=-C_)o-ArXS+-;3D5a=Dj_LQZZH!`%|g#Q5V`wZ6X|D)Tn1|sTi~tbg$NJ+ zTFO%ILsr1OdPV$D*xoz?BvO;D|B|r%igo8mP!oEp z<+~yZxexn7gUqPnf1;_IDpQ94mFbPlKU$&uM=P>fXx28Zlfn{4f5NGYgg%OH8v8z)j^3zz(`}5m zR$`3nU>vr?&9j&OR#^TMX7GiceFMwAA#{hQs9QWXwvuRICH-=O(kjLs{NrQcyyz_R6;DgGm|90RU z)9U*l8ow{{f(RqAqlM-^IV4qJ*m!O^^L;IZ znlWMTpKPL(6(%*jeV!UT+0_No$6(ga4NS_Rk6ixK?m2L`H6ELuug2F2betV%Lh!#o zM-Y&bV1s44sDW!hnW*zXv^6=HKkt<@&HN`v+#eJj-zpBiDE^UZwIlX$_QymqE5OYOt;?839Kj@;jv``F{i(S)Ntbh* z)#~HaT7pHHDV&MQ1_2+==34pSLsU02U7IHJ63UMdv6b3*wju~ZtS~d=ZTyeox!Cwq zXA+HQDFv4e1H{tlPgcZcNHj8!zI@kFN&ZqNrU(dClz1O<;TKB!4*=qn% zD}53WjOVI#cGoerPnA;u>>uJAi=S<3y%Bw{x&C-1;XNy{GAQI*)ec;FkeEugWAjhw zTr+2pi@_Z%hV&aO;1J`FK1AJ|sz?)}PGSW|E4I0gb3)H4@CwPZ+ zP-PJblF(|Rk!^7AC$qeCS>IH%^Xwepfy5~Vbsv>!)q*ryMS=iXci%}_+6~e?5kThg zW{N;g6vIh$5RVZKXMDz+tJ$!EM3S(FnIcbBrq_nTSyUL{L3UXS7q;?!A2@~l6iIR? zAyK=2{X*1M@&zqkIr(9Ywn@3&1X6BgeY|5hs&6rMp~0AxK=etct)Yp`@c2XDDvN#w zYC{DT#;ig{F;$-aWQd{gPK@AJ#sb&2=(v}lU^(b99jua7a90kQK%G!vVJQwp&NjkqD0cvd46SHtkGpuyaz zUoHK1beR%`#i~%nhx!41vr06V-qHX4mT%f~YG2kiNNU!|N*)Fv8d%*0FS9yFf_0Yv zm6Kjr3elT+!4VJ{DAn-D*^CNzOb!)#POLCEx$KD&Q~PE}9X-=2Pk;abbi-qbzputI z!F0I^bjfX!(`;snzY~R+9+4HfOy7Y@lALgq5?iiUk8^i^#rDyC_=@mA%KuimPPDGV z*jGyNIi9Ip?Uyf; zsvmq9zMwQOjn$z#BLL~p|89F*S~Kg)&cz~mwY9Za4=n@_FsQ!*7G}=StG(lX;8sQY z4dN2(Xo3(|eg08IucawncQowcK<0OJxMU#>tm3Jm_cz+QB+fIpwQ6meoDZ=&T@6w? zRq6mfoTD$`eX|3BSI^e`YixTi1OEg- zYVr-c7u*+9Y@AN=%^1EhCNUkzU^_{vncO#WeL+eiJUM7iUFfLKF$o@Lbf>iz$0Ab2 zM;M#?6Wsp|e23bx6!ClWdJLGjndKNHOpqy65hS@+&Fo<@JH^6cSMwVE7NkZM!(&BV zyoumrw0YZ2@)&VWZBzByH$!-iyWQh2<~qe)>dVCJW%Rbca?}|nG}y_QI*OuE;aS9D zw>vpxL6nTk?NN`9T}=53d1bN)=Uz`kGKpnGF(I20%AUa;w1@V2M zk;p7n)@B~OqF&f|+2GZU_vwL`dHcMxf8BRHH%-k`jtz6_ft)19sw`oXt*q4v?td(z zws5^9vpOWTu*`)D%Un?QLPAf9c#cHSm@3yC%UAYgdD}7#K}J^PTLXCYQe0IB_VV+? z^jUkMSIvPoc6jsZc#on6jDHszAqTf`*gz6blSW45K^7V2b_HIbugMHYXMth{devUC{F3ZqrIi+fgsgm}}Zvf3M&td&1< zCv3d>2PbWvknQv<%j;DFJS||~mX{xg@O|#p*|06|i&blcd`j*|0CgHkh4Fw7v%AG>ga{A;-ux zI_aPi2R!c^DpGeeOthpu$9mpPS}c$Gru<&0h|O%5Y!;R4vn9O!vU$z4`D-PdlKxOe z&q$`}Fd+`-=Hl16L&(pe-_-~Dx7ANv&tWuz+yNo@o>swQFff~O&bRTgLMaKkZjId zan=_{fwk!U{pGN62i(h^cC0+=dDeTZoS|?RZ`xu*4mz|mAB}gt-{BBuN&Qtun`^g; zPV$P{-Vh(7`0@6MI{h+0b{%m|>(j49@WQ9jHjNd#?p3676b8rb>H55OreCZUE4;GjF3rnysl|TV^8nUURk< zu&WTxKKl7R7B+kc%S7C~mGBfBEt7dINWk1D^_Y}HT6(p(>3Max$rTPL~=P=`&l57 zTN$x?5eRTd6x4d~IDp_$TprzBxV>)|?Qu@LLoo8#xyESsA+%PZe$-Q&HM|kx!Gc+3lCpVo zWkshe%B;Z3cj*gOA6?U8@NI)=HgoDK9l7(!MsWIZ#;EM8kdGjP3ELj7-|W*AmJ+NX z(Y?Zo(Qrf()0YxjVp#}C-!YANgg7yrdN_*-Mr=l9iVsp0x^U6nt1pqyb2w-33X$%v zRmm{A!zF=BOXEHBg-3jIhx7cb*Pw|4N&oIG3U`NU&q}@M506I%)6>eQs-tRn+U~FS zmHo1c_3__q;4u?YOc+Tv2hxUG?ETzrU82RPBUn^B$n|}8YM~{rLI#Zq^n&HPw*zAw)w?V8KuvmV$^ql`rS=$ zZTQ#wY`Fe}#uH}&VS72Y(ZwdHIE5DUZ@4xur!0}KZ29YM>~E4ez-iw{s~0wJEdVTz zRVa};X2l1Ynmz(u{fs$6Pq%Toh!Y7!vWs2s5laA~QMRr?kOrX4mi7mua|4A;8ls`X zN#0fL3CY#Ro_aOHNv?coC8{=?(<=fcngL5TM7== zDa_)5GuyK|t|`o_6X+KwV)8gnID}pTo4%ZM!vD<5uDj&4w*f6|(7(AYqp>PVu=p*O z9m==tKJC50Yao8P`PCU0TuZ(&Ie0r()U#4g>o3~=)$(BOb80Lat*YVP#9X2V)7V_) zu&9mXxQ8i!CQN4r2-}1ZLQN+tse>=gNTEuyKHT;pru4T(V7+3Z{z6OUY%U_zc z#m*bXJvTaJ`=NPSAJ1q7FoFEnuq)b5zne>xpi$Pg7FP6cjc+kP!@3cJ-$DAvsJ2K{ zZHPhf1&0OHYCQW2SzQIKT&c3NwWysmq4e{Yzv4PrK?4wH=$nZJ+smD>U+WKSSK&Pq z^tTNmq&9^6kAtYD^J!DninuR}I@zZ7*DqZb@M4R`vE-3EE>G%!pLjRRnC5mxpN z(Y9v+MOz|wrJSPD$@sTI6x8jm4g>#rv~`^Y`)KJZY){`hZ_(v{ze}{vk;|7%wJAj~ zNXEbyIEYnZ(+vFyJPOw18AV?_X+sur~_pbv1ECrni44E@` zKb=@7Knj{WVOie2iSS%aR(ysJapp!Q16T+-eUO041+zqnLaIVGeM=RRL)Je$F?GUP zzQ7^QbM82~>90@93Gu3e(xt>+6+&etXzu&+CYB5NYHG>iHVy^w&*6Lcq~S&ZAJd)b zi%{!EI0BRlAM0m% z`}~j1M{diazgE*b*$6)bJJ=l^-rM;HG|S0c?($5tS~aJa6gPGTTfLyBGSxTDJ?gca zC2@#Wn}0Gd^wStY59d|1hrda($A`>OM}}ujGZYR6I3WQ<57q+MjcLIkd`FXlcWEfy zF?Is3r6q)fbfQ2d_V{pL;yYlqE)n;l69$qZYOiCYc|Z39lCNo#%yBj$_Ni6^iz2ev zSST%4K}!Lr^dlc~B0*pn57fj%7cHS?i-_eVfOGdVj*<`Z?BIIkU`q7#8O)my0BTY} zeZ4HOqC4T)1tV-d8%KMiOyD;&F`8N90kV%|&< z_DayRL+0szc+xFGWrXHch#fukvWVx3Y4v0!?a3ds$dSN6(GPx(YaqgO;-511aU8ZvFOi1K8KGb ziVw%2+`tk=1K`D+uFOLQYn!_oVUb>A*!{MUfyHyRH(&(vp`UKK=02!NENH{=E50i% z0`vU57VBeWh)!ba38kx<+$~%b5DVD`k z6wSMvjZyM$9JF5xOrV12vR~is%3QF7>I2JA(A}rp;o>OmWqc%a zZc%QT$UopT&XGO{KWY<@CpYMKV?{G z==g-YdXPE=-&Io4a%N0+5=ru{A1nFz&tIubAN>VLsSC7YCn!_!P|e!ZP0b`jJC*)X z{xrlK;3BQ`-hDE;xGJ;WHG-G1ske@?=_y53>=fy+Y{A|t)jfV3ao`f}BWtZ65MdnOw|~$QjTp z<4jdyDM5zhO|YMg5>pIm1*tE*@(_Y1LJD6D3ur#G)A=z$GE(A^UCb8&@!oC+GVm1`OgHh_fWmNIKV7 z@npWwf?RxFcJ-o>4RmQLl#+Wwfj(^B3$pEvYbimrDw}pFR9KIO6kn8pg0-Qu7)?U* z6JvK?G1DPodgrPLfOi56HmhYM)gWj-RoP(UXJTl^FJOaPCSPZvDW~PuR#!_89 zxIh=zkIS{094j_&_Wd0Vt$f@5W;D6i^LBO=W#3 z>DmGp)>k%^{4BLj&QzV_V`SvB9e!BJO3DE&XX?^S$ zM#Y#i=@r=x+JEME;(10S45LL47!ZD$@S$YAPAZ0}PF zvt(@!a)Bz~oQz_55C8?0h)IbXcX+RNQG%i!=_)8np)f)z0#oIn8H>GrSg%$WcHcF- z-aNNJIb-n;(Opl-n6YzEUcl#?*uz*#p5m7>A6_y{X+@%T@8}php~cxK-*hK8)f9ac{B%1yl9xL-+_?phh$dibYYJW7tsD5phZh9gHM)mq-UXJH+j zBf|`HU1VPta{l8q>TxDRsc|H{{%)yH>E87{qhq~4DHoqI7lzPB@Kd??Yn~;JPl88* z6r1&|o)iqNyUjYtCGi}n`jh7EP3#&xOS(V(numVfgCuxfKF@H4tjlTS@qbP*qTd!d zXbzm`L_>fM%`YE(YB&oMlhN_qH8l=b5C4ZQ@@U@=#B`Z+dZF`SPYk-gEQ;BNktR$aW&<{Msyh#2W zIsp&Pj@jw(Ew#S%`1}t#`15*br>yqcjs?pl*i#ufXx_!% z=gy#80WOrl@{Pe@Y>PI1j$YooJlGPB7L!>1MYN98z)10#UC z63A)!>e`{x{tari0t;!G9B|062un-#9_wCkk_%4lSWdgqsGkQphn$G((I;A*<&foo z69rj(uCD{tMd}?3tvD~A2AYF5L(Mfm^kwl+JEy#Dt zA#Ok8xz0UVc$^4f&WB#swq#xEwEwh#UbKAc>Q+Dg$&0BpbEUjOK`vG#ubr`nlFKJ7 zeHlBGS*13^r^E3Ki~L+zyH!Y#^pbapbN)*#%sWOz+fV_wB3BcTd|TM{MPT+%?~-p& z8R@ITt1sFa84Iiq6xU*RQ;CbQmTb+sxz%=C3!sO7C}cJ4Xop#W9G1Oi$m0_*4{x?J zZZNA>fMfc(!6;^Iv%3_>NOgx15K6kPP~FK%RGC zDynv}VMiP|xevMi(XznOrQ`oH|Hn~1P+s?^14UST0p1@zxxwGq0YL$guGX=WE5W1R zKOUbJw`>ath*;Z}VkZi~b`8QkU6+?%P64kUhFH8H3dhCvO((_nP3OMrn|=@5Nd5l@ h{@)(|_i(s=2.32.5 +keyring>=25.6.0 # Secure credential storage + +# UI Framework +flet>=0.28.3 + +# Git operations (required for AI functionality) +GitPython>=3.1.45 + +# AI Providers +openai>=2.8.0 +anthropic>=0.72.1 \ No newline at end of file