diff --git a/client/__pycache__/utils.cpython-38.pyc b/client/__pycache__/utils.cpython-38.pyc deleted file mode 100644 index ee1eba8..0000000 Binary files a/client/__pycache__/utils.cpython-38.pyc and /dev/null differ diff --git a/client/fabric b/client/fabric index 6176fc2..e48e3eb 100755 --- a/client/fabric +++ b/client/fabric @@ -1,6 +1,6 @@ #!/Users/daniel/Cloud/Development/fabric/client/.venv/bin/python3 -from utils import Standalone, Update +from utils import Standalone, Update, Setup import argparse import sys import os @@ -34,9 +34,11 @@ if __name__ == "__main__": parser.add_argument( "--list", "-l", help="List available patterns", action="store_true" ) - parser.add_argument("--update", "-u", help="Update patterns", action="store_true") + parser.add_argument( + "--update", "-u", help="Update patterns", action="store_true") parser.add_argument("--pattern", "-p", help="The pattern (prompt) to use") - parser.add_argument("--apikey", "-a", help="Add an OpenAI key") + parser.add_argument( + "--setup", help="Set up your fabric instance", action="store_true") args = parser.parse_args() home_holder = os.path.expanduser("~") @@ -45,13 +47,11 @@ if __name__ == "__main__": env_file = os.path.join(config, ".env") if not os.path.exists(config): os.makedirs(config) - if args.apikey: - with open(env_file, "w") as f: - f.write(f"OPENAI_API_KEY={args.apikey}") - print(f"OpenAI API key set to {args.apikey}") + if args.setup: + Setup().run() sys.exit() - if not os.path.exists(env_file): - print("No API key found. Use the --apikey option to set the key") + if not os.path.exists(env_file) or not os.path.exists(config_patterns_directory): + print("Please run --setup to set up your API key and download patterns.") sys.exit() if not os.path.exists(config_patterns_directory): Update() diff --git a/client/requirements.txt b/client/requirements.txt index 92914af..46e7ebe 100644 --- a/client/requirements.txt +++ b/client/requirements.txt @@ -13,4 +13,5 @@ flask-socketio flask-sock gunicorn gevent -httpx \ No newline at end of file +httpx +tqdm \ No newline at end of file diff --git a/client/utils.py b/client/utils.py index e8fbe9b..53ba6f8 100644 --- a/client/utils.py +++ b/client/utils.py @@ -4,6 +4,8 @@ from openai import OpenAI import pyperclip import sys from dotenv import load_dotenv +from requests.exceptions import HTTPError +from tqdm import tqdm current_directory = os.path.dirname(os.path.realpath(__file__)) config_directory = os.path.expanduser("~/.config/fabric") @@ -12,7 +14,8 @@ env_file = os.path.join(config_directory, ".env") class Standalone: def __init__(self, args, pattern="", env_file="~/.config/fabric/.env"): - env_file = os.path.expanduser(env_file) # Expand the tilde to the full path + # Expand the tilde to the full path + env_file = os.path.expanduser(env_file) load_dotenv(env_file) try: apikey = os.environ["OPENAI_API_KEY"] @@ -116,43 +119,86 @@ class Standalone: class Update: def __init__(self): - # Initialize with the root API URL self.root_api_url = "https://api.github.com/repos/danielmiessler/fabric/contents/patterns?ref=main" self.config_directory = os.path.expanduser("~/.config/fabric") - self.pattern_directory = os.path.join(self.config_directory, "patterns") - # Ensure local directory exists + self.pattern_directory = os.path.join( + self.config_directory, "patterns") os.makedirs(self.pattern_directory, exist_ok=True) - self.get_github_directory_contents(self.root_api_url, self.pattern_directory) + self.update_patterns() # Call the update process from a method. + + def update_patterns(self): + try: + self.progress_bar = tqdm(desc="Downloading files", unit="file") + self.get_github_directory_contents( + self.root_api_url, self.pattern_directory) + # Close progress bar on success before printing the message. + self.progress_bar.close() + except HTTPError as e: + # Ensure progress bar is closed on HTTPError as well. + self.progress_bar.close() + if e.response.status_code == 403: + print("GitHub API rate limit exceeded. Please wait before trying again.") + sys.exit() + else: + print(f"Failed to download patterns due to an HTTP error: {e}") + sys.exit() # Exit after handling the error. def download_file(self, url, local_path): - """ - Download a file from a URL to a local path. - """ - response = requests.get(url) - response.raise_for_status() # This will raise an exception for HTTP error codes - with open(local_path, "wb") as f: - f.write(response.content) + try: + response = requests.get(url) + response.raise_for_status() + with open(local_path, "wb") as f: + f.write(response.content) + self.progress_bar.update(1) + except HTTPError as e: + print(f"Failed to download file {url}. HTTP error: {e}") + sys.exit() def process_item(self, item, local_dir): - """ - Process an individual item, downloading if it's a file, or processing further if it's a directory. - """ if item["type"] == "file": - print(f"Downloading file: {item['name']} to {local_dir}") - self.download_file( - item["download_url"], os.path.join(local_dir, item["name"]) - ) + self.download_file(item["download_url"], + os.path.join(local_dir, item["name"])) elif item["type"] == "dir": new_dir = os.path.join(local_dir, item["name"]) os.makedirs(new_dir, exist_ok=True) self.get_github_directory_contents(item["url"], new_dir) def get_github_directory_contents(self, api_url, local_dir): - """ - Fetches the contents of a directory in a GitHub repository and downloads files, recursively handling directories. - """ - response = requests.get(api_url) - response.raise_for_status() # This will raise an exception for HTTP error codes - jsonList = response.json() - for item in jsonList: - self.process_item(item, local_dir) + try: + response = requests.get(api_url) + response.raise_for_status() + jsonList = response.json() + for item in jsonList: + self.process_item(item, local_dir) + except HTTPError as e: + if e.response.status_code == 403: + print("GitHub API rate limit exceeded. Please wait before trying again.") + self.progress_bar.close() # Ensure the progress bar is cleaned up properly + else: + print( + f"Failed to fetch directory contents due to an HTTP error: {e}") + + +class Setup: + def __init__(self): + self.config_directory = os.path.expanduser("~/.config/fabric") + self.pattern_directory = os.path.join( + self.config_directory, "patterns") + os.makedirs(self.pattern_directory, exist_ok=True) + self.env_file = os.path.join(self.config_directory, ".env") + + def api_key(self, api_key): + if not os.path.exists(self.env_file): + with open(self.env_file, "w") as f: + f.write(f"OPENAI_API_KEY={api_key}") + print(f"OpenAI API key set to {api_key}") + + def patterns(self): + Update() + sys.exit() + + def run(self): + print("Welcome to Fabric. Let's get started.") + apikey = input("Please enter your OpenAI API key\n") + self.api_key(apikey.strip()) + self.patterns()