From dc5246aabf6143d32749ee15addb635814fa205e Mon Sep 17 00:00:00 2001
From: Meir Michanie <meirm@riunx.com>
Date: Thu, 11 Apr 2024 09:42:28 +0200
Subject: [PATCH] Split stdout from stderr messages

---
 installer/client/cli/fabric.py |  8 ++--
 installer/client/cli/save.py   |  8 ++--
 installer/client/cli/ts.py     | 10 ++---
 installer/client/cli/utils.py  | 75 ++++++++++++++++++----------------
 installer/client/cli/yt.py     | 11 ++---
 5 files changed, 58 insertions(+), 54 deletions(-)

diff --git a/installer/client/cli/fabric.py b/installer/client/cli/fabric.py
index 97fb1cd..1c0d164 100755
--- a/installer/client/cli/fabric.py
+++ b/installer/client/cli/fabric.py
@@ -1,4 +1,4 @@
-from .utils import Standalone, Update, Setup, Alias, run_electron_app
+from .utils import Standalone, Update, Setup, Alias, run_electron_app, eprint
 import argparse
 import sys
 import os
@@ -80,7 +80,7 @@ def main():
         Alias().execute()
         sys.exit()
     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.")
+        eprint("Please run --setup to set up your API key and download patterns.")
         sys.exit()
     if not os.path.exists(config_patterns_directory):
         Update()
@@ -98,7 +98,7 @@ def main():
         sys.exit()
     if args.context:
         if not os.path.exists(os.path.join(config, "context.md")):
-            print("Please create a context.md file in ~/.config/fabric")
+            eprint("Please create a context.md file in ~/.config/fabric")
             sys.exit()
     if args.agents:
         standalone = Standalone(args)
@@ -120,7 +120,7 @@ def main():
                 print(d)
             sys.exit()
         except FileNotFoundError:
-            print("No patterns found")
+            eprint("No patterns found")
             sys.exit()
     if args.listmodels:
         gptmodels, localmodels, claudemodels = standalone.fetch_available_models()
diff --git a/installer/client/cli/save.py b/installer/client/cli/save.py
index eaeef21..282020a 100755
--- a/installer/client/cli/save.py
+++ b/installer/client/cli/save.py
@@ -2,7 +2,7 @@ import argparse
 import os
 import sys
 from datetime import datetime
-
+from utils import eprint
 from dotenv import load_dotenv
 
 DEFAULT_CONFIG = "~/.config/fabric/.env"
@@ -15,20 +15,20 @@ load_dotenv(os.path.expanduser(DEFAULT_CONFIG))
 def main(tag, tags, silent, fabric):
     out = os.getenv(PATH_KEY)
     if out is None:
-        print(f"'{PATH_KEY}' not set in {DEFAULT_CONFIG} or in your environment.")
+        eprint(f"'{PATH_KEY}' not set in {DEFAULT_CONFIG} or in your environment.")
         sys.exit(1)
 
     out = os.path.expanduser(out)
 
     if not os.path.isdir(out):
-        print(f"'{out}' does not exist. Create it and try again.")
+        eprint(f"'{out}' does not exist. Create it and try again.")
         sys.exit(1)
 
     if not out.endswith("/"):
         out += "/"
 
     if len(sys.argv) < 2:
-        print(f"'{sys.argv[0]}' takes a single argument to tag your summary")
+        eprint(f"'{sys.argv[0]}' takes a single argument to tag your summary")
         sys.exit(1)
 
     yyyymmdd = datetime.now().strftime(DATE_FORMAT)
diff --git a/installer/client/cli/ts.py b/installer/client/cli/ts.py
index fa98ba2..0193084 100644
--- a/installer/client/cli/ts.py
+++ b/installer/client/cli/ts.py
@@ -3,7 +3,7 @@ from pydub import AudioSegment
 from openai import OpenAI
 import os
 import argparse
-
+from utils import eprint
 
 class Whisper:
     def __init__(self):
@@ -14,10 +14,10 @@ class Whisper:
             self.client = OpenAI()
             self.client.api_key = apikey
         except KeyError:
-            print("OPENAI_API_KEY not found in environment variables.")
+            eprint("OPENAI_API_KEY not found in environment variables.")
 
         except FileNotFoundError:
-            print("No API key found. Use the --apikey option to set the key")
+            eprint("No API key found. Use the --apikey option to set the key")
         self.whole_response = []
 
     def split_audio(self, file_path):
@@ -66,7 +66,7 @@ class Whisper:
             self.whole_response.append(response.text)
 
         except Exception as e:
-            print(f"Error: {e}")
+            eprint(f"Error: {e}")
 
     def process_file(self, audio_file):
         """        Transcribe an audio file and print the transcript.
@@ -94,7 +94,7 @@ class Whisper:
             print(' '.join(self.whole_response))
 
         except Exception as e:
-            print(f"Error: {e}")
+            eprint(f"Error: {e}")
 
 
 def main():
diff --git a/installer/client/cli/utils.py b/installer/client/cli/utils.py
index 5474df5..70f34b9 100644
--- a/installer/client/cli/utils.py
+++ b/installer/client/cli/utils.py
@@ -11,12 +11,15 @@ import tempfile
 import subprocess
 import shutil
 from youtube_transcript_api import YouTubeTranscriptApi
-
+import sys
 current_directory = os.path.dirname(os.path.realpath(__file__))
 config_directory = os.path.expanduser("~/.config/fabric")
 env_file = os.path.join(config_directory, ".env")
 
 
+def eprint(*args, **kwargs):
+    print(*args, file=sys.stderr, **kwargs)
+
 class Standalone:
     def __init__(self, args, pattern="", env_file="~/.config/fabric/.env"):
         """        Initialize the class with the provided arguments and environment file.
@@ -143,7 +146,7 @@ class Standalone:
                     system_message = {"role": "system", "content": system}
                 messages = [system_message, user_message]
             except FileNotFoundError:
-                print("pattern not found")
+                eprint("pattern not found")
                 return
         else:
             if context:
@@ -183,17 +186,17 @@ class Standalone:
                     sys.stdout.flush()
         except Exception as e:
             if "All connection attempts failed" in str(e):
-                print(
+                eprint(
                     "Error: cannot connect to llama2. If you have not already, please visit https://ollama.com for installation instructions")
             if "CLAUDE_API_KEY" in str(e):
-                print(
+                eprint(
                     "Error: CLAUDE_API_KEY not found in environment variables. Please run --setup and add the key")
             if "overloaded_error" in str(e):
-                print(
+                eprint(
                     "Error: Fabric is working fine, but claude is overloaded. Please try again later.")
             else:
-                print(f"Error: {e}")
-                print(e)
+                eprint(f"Error: {e}")
+                eprint(e)
         if self.args.copy:
             pyperclip.copy(buffer)
         if self.args.output:
@@ -229,7 +232,7 @@ class Standalone:
                     system_message = {"role": "system", "content": system}
                 messages = [system_message, user_message]
             except FileNotFoundError:
-                print("pattern not found")
+                eprint("pattern not found")
                 return
         else:
             if context:
@@ -262,19 +265,19 @@ class Standalone:
                         f.write(response.choices[0].message.content)
         except Exception as e:
             if "All connection attempts failed" in str(e):
-                print(
+                eprint(
                     "Error: cannot connect to llama2. If you have not already, please visit https://ollama.com for installation instructions")
             if "CLAUDE_API_KEY" in str(e):
-                print(
+                eprint(
                     "Error: CLAUDE_API_KEY not found in environment variables. Please run --setup and add the key")
             if "overloaded_error" in str(e):
-                print(
+                eprint(
                     "Error: Fabric is working fine, but claude is overloaded. Please try again later.")
             if "Attempted to call a sync iterator on an async stream" in str(e):
-                print("Error: There is a problem connecting fabric with your local ollama installation. Please visit https://ollama.com for installation instructions. It is possible that you have chosen the wrong model. Please run fabric --listmodels to see the available models and choose the right one with fabric --model <model> or fabric --changeDefaultModel. If this does not work. Restart your computer (always a good idea) and try again. If you are still having problems, please visit https://ollama.com for installation instructions.")
+                eprint("Error: There is a problem connecting fabric with your local ollama installation. Please visit https://ollama.com for installation instructions. It is possible that you have chosen the wrong model. Please run fabric --listmodels to see the available models and choose the right one with fabric --model <model> or fabric --changeDefaultModel. If this does not work. Restart your computer (always a good idea) and try again. If you are still having problems, please visit https://ollama.com for installation instructions.")
             else:
-                print(f"Error: {e}")
-                print(e)
+                eprint(f"Error: {e}")
+                eprint(e)
 
     def fetch_available_models(self):
         gptlist = []
@@ -299,7 +302,7 @@ class Standalone:
         except APIConnectionError as e:
             pass
         except Exception as e:
-            print(f"Error: {getattr(e.__context__, 'args', [''])[0]}")
+            eprint(f"Error: {getattr(e.__context__, 'args', [''])[0]}")
             sys.exit()
 
         import ollama
@@ -347,9 +350,9 @@ class Standalone:
             os.environ["OPENAI_API_KEY"] = "NA"
 
         elif model in self.claudeList:
-            print("Claude is not supported in this mode")
+            eprint("Claude is not supported in this mode")
             sys.exit()
-        print("Starting PraisonAI...")
+        eprint("Starting PraisonAI...")
         praison_ai = PraisonAI(auto=userInput, framework="autogen")
         praison_ai.main()
 
@@ -362,7 +365,7 @@ class Update:
         self.pattern_directory = os.path.join(
             self.config_directory, "patterns")
         os.makedirs(self.pattern_directory, exist_ok=True)
-        print("Updating patterns...")
+        eprint("Updating patterns...")
         self.update_patterns()  # Start the update process immediately
 
     def update_patterns(self):
@@ -390,9 +393,9 @@ class Update:
                             shutil.move(custom_path, patterns_source_path)
                     shutil.rmtree(self.pattern_directory)
                 shutil.copytree(patterns_source_path, self.pattern_directory)
-                print("Patterns updated successfully.")
+                eprint("Patterns updated successfully.")
             else:
-                print("Patterns folder not found in the downloaded zip.")
+                eprint("Patterns folder not found in the downloaded zip.")
 
     def download_zip(self, url, save_path):
         """Download the zip file from the specified URL."""
@@ -400,13 +403,13 @@ class Update:
         response.raise_for_status()  # Check if the download was successful
         with open(save_path, 'wb') as f:
             f.write(response.content)
-        print("Downloaded zip file successfully.")
+        eprint("Downloaded zip file successfully.")
 
     def extract_zip(self, zip_path, extract_to):
         """Extract the zip file to the specified directory."""
         with zipfile.ZipFile(zip_path, 'r') as zip_ref:
             zip_ref.extractall(extract_to)
-        print("Extracted zip file successfully.")
+        eprint("Extracted zip file successfully.")
         return extract_to  # Return the path to the extracted contents
 
 
@@ -485,7 +488,7 @@ class Setup:
         if not os.path.exists(self.env_file) and api_key:
             with open(self.env_file, "w") as f:
                 f.write(f"OPENAI_API_KEY={api_key}\n")
-            print(f"OpenAI API key set to {api_key}")
+            eprint(f"OpenAI API key set to {api_key}")
         elif api_key:
             # erase the line OPENAI_API_KEY=key and write the new key
             with open(self.env_file, "r") as f:
@@ -558,7 +561,7 @@ class Setup:
         gpt, ollama, claude = standalone.fetch_available_models()
         allmodels = gpt + ollama + claude
         if model not in allmodels:
-            print(
+            eprint(
                 f"Error: {model} is not a valid model. Please run fabric --listmodels to see the available models.")
             sys.exit()
 
@@ -586,10 +589,10 @@ class Setup:
                     if not there:
                         f.write(f'DEFAULT_MODEL={model}\n')
 
-                print(
+                eprint(
                     f"Default model changed to {model}. Please restart your terminal to use it.")
             else:
-                print("No shell configuration file found.")
+                eprint("No shell configuration file found.")
 
     def patterns(self):
         """        Method to update patterns and exit the system.
@@ -609,14 +612,14 @@ class Setup:
             None
         """
 
-        print("Welcome to Fabric. Let's get started.")
+        eprint("Welcome to Fabric. Let's get started.")
         apikey = input(
             "Please enter your OpenAI API key. If you do not have one or if you have already entered it, press enter.\n")
         self.api_key(apikey)
-        print("Please enter your claude API key. If you do not have one, or if you have already entered it, press enter.\n")
+        eprint("Please enter your claude API key. If you do not have one, or if you have already entered it, press enter.\n")
         claudekey = input()
         self.claude_key(claudekey)
-        print("Please enter your YouTube API key. If you do not have one, or if you have already entered it, press enter.\n")
+        eprint("Please enter your YouTube API key. If you do not have one, or if you have already entered it, press enter.\n")
         youtubekey = input()
         self.youtube_key(youtubekey)
         self.patterns()
@@ -649,7 +652,7 @@ class Transcribe:
                 transcript += segment['text'] + " "
             return transcript.strip()
         except Exception as e:
-            print("Error:", e)
+            eprint("Error:", e)
             return None
 
 
@@ -661,7 +664,7 @@ class AgentSetup:
             None
         """
 
-        print("Welcome to Fabric. Let's get started.")
+        eprint("Welcome to Fabric. Let's get started.")
         browserless = input("Please enter your Browserless API key\n").strip()
         serper = input("Please enter your Serper API key\n").strip()
 
@@ -689,7 +692,7 @@ def run_electron_app():
     # Step 2: Check for the './installer/client/gui' directory
     target_dir = '../gui'
     if not os.path.exists(target_dir):
-        print(f"""The directory {
+        eprint(f"""The directory {
               target_dir} does not exist. Please check the path and try again.""")
         return
 
@@ -698,7 +701,7 @@ def run_electron_app():
         subprocess.run(['npm', '--version'], check=True,
                        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
     except subprocess.CalledProcessError:
-        print("NPM is not installed. Please install NPM and try again.")
+        eprint("NPM is not installed. Please install NPM and try again.")
         return
 
     # If this point is reached, NPM is installed.
@@ -707,10 +710,10 @@ def run_electron_app():
 
     # Step 5: Run 'npm install' and 'npm start'
     try:
-        print("Running 'npm install'... This might take a few minutes.")
+        eprint("Running 'npm install'... This might take a few minutes.")
         subprocess.run(['npm', 'install'], check=True)
-        print(
+        eprint(
             "'npm install' completed successfully. Starting the Electron app with 'npm start'...")
         subprocess.run(['npm', 'start'], check=True)
     except subprocess.CalledProcessError as e:
-        print(f"An error occurred while executing NPM commands: {e}")
+        eprint(f"An error occurred while executing NPM commands: {e}")
diff --git a/installer/client/cli/yt.py b/installer/client/cli/yt.py
index b875547..fba6b15 100644
--- a/installer/client/cli/yt.py
+++ b/installer/client/cli/yt.py
@@ -8,6 +8,7 @@ import json
 import isodate
 import argparse
 import sys
+from utils import eprint
 
 
 def get_video_id(url):
@@ -51,7 +52,7 @@ def get_comments(youtube, video_id):
                 request = None
 
     except HttpError as e:
-        print(f"Failed to fetch comments: {e}")
+        eprint(f"Failed to fetch comments: {e}")
 
     return comments
 
@@ -64,13 +65,13 @@ def main_function(url, options):
     # Get YouTube API key from environment variable
     api_key = os.getenv("YOUTUBE_API_KEY")
     if not api_key:
-        print("Error: YOUTUBE_API_KEY not found in ~/.config/fabric/.env")
+        eprint("Error: YOUTUBE_API_KEY not found in ~/.config/fabric/.env")
         return
 
     # Extract video ID from URL
     video_id = get_video_id(url)
     if not video_id:
-        print("Invalid YouTube URL")
+        eprint("Invalid YouTube URL")
         return
 
     try:
@@ -116,7 +117,7 @@ def main_function(url, options):
             # Print JSON object
             print(json.dumps(output, indent=2))
     except HttpError as e:
-        print(f"Error: Failed to access YouTube API. Please check your YOUTUBE_API_KEY and ensure it is valid: {e}")
+        eprint(f"Error: Failed to access YouTube API. Please check your YOUTUBE_API_KEY and ensure it is valid: {e}")
 
 
 def main():
@@ -131,7 +132,7 @@ def main():
     args = parser.parse_args()
 
     if args.url is None:
-        print("Error: No URL provided.")
+        eprint("Error: No URL provided.")
         return
 
     main_function(args.url, args)