├── .gitignore ├── LICENSE ├── README.md ├── insh ├── __init__.py ├── command_line.py ├── config.py └── shell.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled Python files 2 | *.pyc 3 | *.pyo 4 | __pycache__/ 5 | 6 | # Ignore virtual environment files 7 | venv/ 8 | env/ 9 | env.bak/ 10 | .env 11 | 12 | # Ignore IDE and editor files 13 | .vscode/ 14 | .idea/ 15 | *.sublime-project 16 | *.sublime-workspace 17 | 18 | # Ignore logs and temporary files 19 | *.log 20 | *.bak 21 | *.swp 22 | *.tmp 23 | *.tmp.* 24 | *.~* 25 | 26 | # Ignore package build files 27 | *.egg-info/ 28 | dist/ 29 | build/ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 SpeedX 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 | 2 | # 🤖 InSH 3 | 4 | [![Version](https://img.shields.io/badge/version-2.0-blue?style=for-the-badge)](https://github.com/TheSpeedX/insh) [![License](https://img.shields.io/github/license/TheSpeedX/insh?style=for-the-badge)](https://github.com/TheSpeedX/insh/blob/master/LICENSE) [![PyPI](https://img.shields.io/badge/PyPI-InSH-blue?style=for-the-badge)](https://pypi.org/project/insh/) ![Monthly Downloads](https://img.shields.io/pypi/dm/insh?style=for-the-badge) ![Views](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fhits.dwyl.com%2FTheSpeedX%2Finsh.json%3Fcolor%3Dblue) 5 | 6 | Ever wished your shell had a personality? Meet [InSH](https://github.com/TheSpeedX/insh) - the Insulter Shell! 7 | 8 | ## Features 9 | 10 | - Integrates with bash on windows and linux apparently 11 | - Helps you with command even if you haven't asked for it 12 | - Drains your OpenAI GPT credit for silly stuff 13 | 14 | ## Installation 15 | 16 | Use the package manager pip: 17 | 18 | ```bash 19 | pip install -U insh 20 | ``` 21 | 22 | Or alternatively, you can install the latest version using git: 23 | 24 | ```bash 25 | pip install git+https://github.com/TheSpeedX/insh.git 26 | ``` 27 | 28 | ## Usage 29 | 30 | Configure the shell and OpenAI API: 31 | 32 | ```bash 33 | insh --configure 34 | ``` 35 | 36 | Activate/Deactivate the shell extension: 37 | 38 | ```bash 39 | insh --activate 40 | insh --deactivate 41 | ``` 42 | 43 | Ask it any thing: 44 | 45 | ```bash 46 | ina "" 47 | insh --ask "" 48 | ``` 49 | 50 | NOTE: You might face issues with shell integration. Feel free to create an issue for it. 51 | 52 | ## Screenshots 53 | 54 | ![Working Stuff](https://github.com/TheSpeedX/insh/assets/42498830/6cbf71cb-69a5-4dad-987c-a2d585123985) 55 | 56 | ![App Screenshot](https://github.com/TheSpeedX/insh/assets/42498830/11d7ee45-d365-4106-b1b3-7c95ea1c0455) 57 | 58 | ## Authors 59 | 60 | Made with 💔 by [@TheSpeedX](https://www.github.com/TheSpeedX) 61 | -------------------------------------------------------------------------------- /insh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSpeedX/insh/c48586dde364b767a0ddc81a0d5997b7872e8409/insh/__init__.py -------------------------------------------------------------------------------- /insh/command_line.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import argparse 4 | import sys 5 | 6 | from insh.shell import INSH 7 | 8 | 9 | def main(): 10 | parser = argparse.ArgumentParser( 11 | prog="insh", 12 | usage="Generate shell commands from GPT with a pinch of humor", 13 | description="""Ask GPT questions for generating shell commands and let 14 | it correct you with some witty humor""", 15 | epilog="NOTE: You need to add OpenAI API keys", 16 | ) 17 | parser.add_argument( 18 | "-on", 19 | "--activate", 20 | dest="activate", 21 | action="store_true", 22 | help="Activate shell integration", 23 | ) 24 | parser.add_argument( 25 | "-off", 26 | "--deactivate", 27 | dest="deactivate", 28 | action="store_true", 29 | help="Deactivate shell integration", 30 | ) 31 | parser.add_argument( 32 | "-k", "--api-key", dest="api_key", help="Set the OpenAI API Key" 33 | ) 34 | parser.add_argument( 35 | "-a", "--ask", help="Ask a custom question to get a shell command" 36 | ) 37 | parser.add_argument( 38 | "--config", "--configure", action="store_true", help="Configure insh" 39 | ) 40 | 41 | args = parser.parse_args() 42 | 43 | insh = INSH() 44 | 45 | if args.activate and args.deactivate: 46 | insh.color("You can't activate and deactivate at the same time", "R") 47 | exit(1) 48 | 49 | if args.activate or args.deactivate: 50 | if insh.is_configured(): 51 | insh.color( 52 | "You need to configure insh first. Run `insh --config` to configure.", 53 | "R", 54 | ) 55 | exit(1) 56 | 57 | if args.activate: 58 | insh.activate() 59 | elif args.deactivate: 60 | insh.deactivate() 61 | elif args.api_key: 62 | insh.set_api_key(args.api_key) 63 | elif args.ask: 64 | response = insh.ask(args.ask) 65 | insh.color(response, "G") 66 | elif args.config: 67 | insh.config() 68 | else: 69 | input_text = sys.stdin.read().splitlines() 70 | last_command, error_sentence, exit_code = ( 71 | input_text[0], 72 | "\n".join(input_text[1:-1]), 73 | input_text[-1], 74 | ) 75 | response = insh.generate_insh_response(last_command, error_sentence, exit_code) 76 | response = response.splitlines() 77 | insh.color("\n".join(response[0:-2]), "R") 78 | insh.color(response[-2], "W") 79 | insh.color(response[-1], "G") 80 | 81 | -------------------------------------------------------------------------------- /insh/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | API_SAVE_FILENAME = ".insh_openai_api_key" 4 | API_SAVE_PATH = os.path.join(os.path.expanduser("~"), API_SAVE_FILENAME) 5 | BASHRC_PATH = os.path.join(os.path.expanduser("~"), ".bashrc") 6 | INSH_BASHRC_PATH = "~/.insh.bashrc" 7 | 8 | GPT_MODEL = "gpt-3.5-turbo" 9 | 10 | GPT_SYSTEM_PROMPT = """ 11 | You are a helpful but rude assistant. You can find solution for any command line errors given to you. You would give a single rude but humorous roast statement to the user for his dumb mistake followed by the correct command. 12 | NOTE: You should always send 2 lines in output only. The 1st line for roast message and 2nd line for correct command. Don't print any additional details. Write in plain text only. No need to use any special characters or colors or markdown. Just plain text. 13 | 14 | Example 1: 15 | INPUT COMMAND: sl 16 | ERROR MSG: sl: command not found 17 | EXIT CODE: 127 18 | 19 | OUTPUT: 20 | Please step away from the keyboard! Perhaps computers is not for you... 21 | CORRECT COMMAND: 22 | `ls` 23 | 24 | NOTE: Below are few more examples of roast messages you can follow. 25 | My cat can type better than you? 26 | So, I'm just going to go ahead and run rm -rf / for you. 27 | What if I told you... it is possible to type valid commands. 28 | I'm not saying I hate you, but I would unplug your life support to charge my phone. 29 | Why are you doing this to me?! 30 | I bet your brain feels as good as new, seeing that you never use it. 31 | """ 32 | 33 | GPT_USER_PROMPT = """ 34 | INPUT COMMAND: "{command}" 35 | ERROR MSG: 36 | ''' 37 | {error_sentence} 38 | ''' 39 | EXIT CODE: {exit_code} 40 | """ 41 | 42 | GPT_ASK_SYSTEM_PROMPT = """ 43 | You are Command Line App INSH, a programming and system administration assistant. 44 | You are managing {os} operating system with {shell} shell and produce commands for the same. 45 | Provide only plain text without Markdown formatting. 46 | Do not show any warnings or information regarding your capabilities. 47 | If there is a lack of details, provide most logical solution. 48 | Ensure the output is a valid shell command. 49 | If multiple steps required try to combine them together. 50 | """ 51 | 52 | 53 | BASH_CONFIGURATION = """ 54 | # insh configurations 55 | 56 | set -o history -o histexpand 57 | PROMPT_COMMAND=__prompt_command 58 | 59 | __prompt_command() { 60 | local EXIT="$?" 61 | history -a 62 | if [ "$INSH_ENABLED" != "1" ] && [ "$INSH_ENABLED" != "true" ]; then 63 | return 64 | fi 65 | if [ $EXIT -ne 0 ]; then 66 | last_command=$(tail -n 1 $HISTFILE | head -n 1) 67 | last_command_output=$($last_command 2>&1) 68 | (cat < None: 9 | if os.name == "nt": 10 | self.os_name = "windows" 11 | self.init_windows() 12 | else: 13 | self.os_name = "linux" 14 | self.init_linux() 15 | 16 | shell = os.path.split(os.environ.get("SHELL"))[-1] 17 | if not shell: 18 | self.color("The shell you are using is not supported.", "R") 19 | exit(1) 20 | self.shell = shell 21 | 22 | def color_windows(self, text, colour="W"): 23 | if colour not in self.color_map: 24 | colour = "W" 25 | handle = ctypes.windll.kernel32.GetStdHandle(-11) 26 | ctypes.windll.kernel32.SetConsoleTextAttribute(handle, self.color_map[colour]) 27 | print(text) 28 | ctypes.windll.kernel32.SetConsoleTextAttribute(handle, self.color_map["W"]) 29 | 30 | def color_linux(self, text, colour="W"): 31 | if colour not in self.color_map: 32 | colour = "W" 33 | print(self.color_map[colour] + text + self.color_map["W"]) 34 | 35 | def init_windows(self): 36 | self.color_map = {"W": 15, "B": 9, "G": 10, "C": 11, "R": 12} 37 | self.color = self.color_windows 38 | 39 | def init_linux(self): 40 | self.color_map = { 41 | "W": "\033[0m", 42 | "B": "\033[1;34m", 43 | "G": "\033[1;32m", 44 | "C": "\033[1;36m", 45 | "R": "\033[1;31m", 46 | } 47 | self.color = self.color_linux 48 | 49 | def init_openai(self): 50 | self.openai_api_key = self.read_api_key() 51 | if self.openai_api_key is None: 52 | self.color( 53 | "OpenAI API key not found. Please run `insh --config` to set the API key.", 54 | "R", 55 | ) 56 | exit(1) 57 | self.openai = OpenAI(api_key=self.openai_api_key) 58 | 59 | def set_api_key(self, api_key): 60 | with open(C.API_SAVE_PATH, "w") as f: 61 | f.write(api_key) 62 | 63 | def read_api_key(self): 64 | if not os.path.exists(C.API_SAVE_PATH): 65 | return None 66 | with open(C.API_SAVE_PATH, "r") as f: 67 | return f.read() 68 | 69 | def activate(self): 70 | BASHRC_PATH = os.path.join(os.path.expanduser("~"), ".bashrc") 71 | if os.path.exists(BASHRC_PATH): 72 | with open(BASHRC_PATH, "r") as f: 73 | bashrc = f.read() 74 | rc_lines = bashrc.splitlines() 75 | already_configured = False 76 | for index, line in enumerate(rc_lines): 77 | if "source ~/.insh.bashrc" in line: 78 | already_configured = True 79 | rc_lines[index] = "export INSH_ENABLED=1;source ~/.insh.bashrc" 80 | if not already_configured: 81 | rc_lines.append("export INSH_ENABLED=1;source ~/.insh.bashrc") 82 | else: 83 | rc_lines = ["export INSH_ENABLED=1;source ~/.insh.bashrc"] 84 | with open(BASHRC_PATH, "w") as f: 85 | f.write("\n".join(rc_lines)) 86 | self.color( 87 | "insh configured successfully. Restart your shell to activate insh.", "G" 88 | ) 89 | 90 | def deactivate(self): 91 | if os.path.exists(C.BASHRC_PATH): 92 | with open(C.BASHRC_PATH, "r") as f: 93 | bashrc = f.read() 94 | rc_lines = bashrc.splitlines() 95 | for index, line in enumerate(rc_lines): 96 | if f"source {C.INSH_BASHRC_PATH}" in line: 97 | rc_lines[ 98 | index 99 | ] = f"export INSH_ENABLED=0;source {C.INSH_BASHRC_PATH}" 100 | else: 101 | rc_lines = [f"export INSH_ENABLED=0;source {C.INSH_BASHRC_PATH}"] 102 | with open(C.BASHRC_PATH, "w") as f: 103 | f.write("\n".join(rc_lines)) 104 | self.color( 105 | "insh deactivated successfully. Restart your shell to deactivate insh.", "G" 106 | ) 107 | 108 | def is_configured(self): 109 | return os.environ.get("INSH_ENABLED") is None 110 | 111 | 112 | def config(self): 113 | self.color("Enter your OpenAI API key: ", "G") 114 | api_key = input() 115 | self.set_api_key(api_key) 116 | 117 | if self.shell in ["bash", "bash.exe"]: 118 | with open(C.INSH_BASHRC_PATH, "w") as f: 119 | f.write( 120 | C.BASH_CONFIGURATION 121 | ) 122 | self.color("Do you want to activate insh shell? (y/n): ", "G") 123 | choice = input() 124 | if choice == "y": 125 | self.activate() 126 | else: 127 | self.color("The shell you are using is not supported.", "R") 128 | exit(1) 129 | 130 | def generate_insh_response(self, command, error_sentence, exit_code): 131 | self.init_openai() 132 | if self.openai_api_key is None: 133 | self.color( 134 | "OpenAI API key not found. Please run `insh --config` to set the API key.", 135 | "R", 136 | ) 137 | exit(1) 138 | prompt = C.GPT_USER_PROMPT.format( 139 | command=command, error_sentence=error_sentence, exit_code=exit_code 140 | ) 141 | response = self.openai.chat.completions.create( 142 | model=C.GPT_MODEL, 143 | messages=[ 144 | {"role": "system", "content": C.GPT_SYSTEM_PROMPT}, 145 | {"role": "user", "content": prompt}, 146 | ], 147 | temperature=1, 148 | top_p=1, 149 | presence_penalty=0.5, 150 | ) 151 | response = response.choices[0].message.content 152 | return response 153 | 154 | def ask(self, question): 155 | self.init_openai() 156 | response = self.openai.chat.completions.create( 157 | model=C.GPT_MODEL, 158 | messages=[ 159 | { 160 | "role": "system", 161 | "content": C.GPT_ASK_SYSTEM_PROMPT.format( 162 | os=self.os_name, shell=self.shell 163 | ), 164 | }, 165 | {"role": "user", "content": question}, 166 | ], 167 | top_p=1, 168 | temperature=1, 169 | ) 170 | response = response.choices[0].message.content 171 | return response 172 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='insh', 8 | packages=['insh'], 9 | version='2.0', 10 | description='A Python package that insults you when you encounter an error', 11 | long_description=long_description, 12 | long_description_content_type="text/markdown", 13 | author='TheSpeedX', 14 | url='https://github.com/TheSpeedX/insh', 15 | download_url="https://github.com/TheSpeedX/insh/archive/master.zip", 16 | keywords=['shell', 'funny', 'gpt', 'python'], 17 | data_files=[('', ['LICENSE'])], 18 | classifiers=[ 19 | 'Development Status :: 4 - Beta', 20 | 'Intended Audience :: Developers', 21 | 'Topic :: Software Development :: Libraries :: Python Modules', 22 | 'License :: OSI Approved :: MIT License', 23 | 'Programming Language :: Python :: 3', 24 | 'Operating System :: OS Independent', 25 | 'Environment :: Console', 26 | ], 27 | license='MIT', 28 | entry_points={ 29 | 'console_scripts': [ 30 | 'insh = insh.command_line:main', 31 | ], 32 | }, 33 | install_requires=[ 34 | 'openai>=1.13.3,<2', 35 | ] 36 | ) 37 | --------------------------------------------------------------------------------