├── .env-example ├── .github └── ISSUE_TEMPLATE │ └── user-story.md ├── .gitignore ├── LICENSE ├── README.md ├── main.py ├── requirements.txt ├── shell-command-generator-sequence.png ├── shell-command-generator-sequence.puml └── test_main.py /.env-example: -------------------------------------------------------------------------------- 1 | ANTHROPIC_API_KEY= 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/user-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User Story 3 | about: This template is used to document user stories. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | As a [role], 11 | I want [goal or desire], 12 | so that [benefit or reason]. 13 | 14 | Description 15 | 16 | Provide a clear and concise description of the user story. Explain the context, what it entails, and any important details or constraints. 17 | 18 | Acceptance Criteria 19 | 20 | • Define clear, measurable conditions that must be met for this story to be complete. 21 | • Example: Users can [specific action] under [specific condition]. 22 | • List as many criteria as necessary. 23 | 24 | Tasks 25 | 26 | - [ ] Task 1 description 27 | - [ ] Task 2 description 28 | 29 | 30 | Additional Notes 31 | 32 | Include any relevant resources, links, or comments to help complete this story effectively. 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .aider* 2 | venv/ 3 | __pycache__/ 4 | *.pyc 5 | .env 6 | .VSCodeCounter/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tim Kitchens 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 | # Shell Command Generator 2 | 3 | ## Description 4 | 5 | Shell Command Generator is a Python-based CLI application that generates shell commands based on user queries using the Anthropic API. It supports multiple shell environments and provides an interactive interface for users to input their command queries and receive generated commands. 6 | 7 | ## Technologies Used 8 | 9 | - Python 3.x 10 | - Click (for CLI interface) 11 | - Anthropic API with Claude 3.5 Sonnet model (for command generation) 12 | - python-dotenv (for environment variable management) 13 | - pyperclip (for clipboard functionality) 14 | 15 | ## How It Works 16 | 17 | 1. The user selects their preferred shell environment (cmd, powershell, or bash). 18 | 2. The user inputs a query describing the command they need. 19 | 3. The application sends the query to the Anthropic API using the Claude 3.5 Sonnet model, which generates a suitable shell command. 20 | 4. The generated command is displayed to the user and can be copied to the clipboard. 21 | 5. The process repeats until the user chooses to exit. 22 | 23 | ## Installation 24 | 25 | 1. Clone this repository: 26 | ``` 27 | git clone https://github.com/timkitch/yt-ai-shell-command-generator.git 28 | cd shell-command-generator 29 | ``` 30 | 31 | 2. Create a virtual environment and activate it: 32 | ``` 33 | python -m venv venv 34 | source venv/bin/activate # On Windows, use `venv\Scripts\activate` 35 | ``` 36 | 37 | 3. Install the required packages: 38 | ``` 39 | pip install -r requirements.txt 40 | ``` 41 | 42 | 4. Create a `.env` file in the project root and add your Anthropic API key: 43 | ``` 44 | ANTHROPIC_API_KEY=your_api_key_here 45 | ``` 46 | 47 | ## Obtaining an Anthropic API Key 48 | 49 | To use this project, you'll need an API key from Anthropic. Here's how to get one: 50 | 51 | 1. Go to the Anthropic website (https://www.anthropic.com) 52 | 2. Sign up for an account or log in if you already have one 53 | 3. Navigate to the API section of your account dashboard 54 | 4. Generate a new API key 55 | 5. Copy the API key and paste it into your `.env` file as shown in the installation steps 56 | 57 | Note: Keep your API key confidential and never share it publicly. 58 | 59 | ## Usage 60 | 61 | 1. Run the application: 62 | ``` 63 | python main.py 64 | ``` 65 | 66 | 2. Select your preferred shell environment when prompted. 67 | 68 | 3. Enter your command queries when prompted. The application will generate and display the corresponding shell commands. 69 | 70 | 4. Choose whether to copy the generated command to your clipboard. 71 | 72 | 5. Type 'exit' when you're done to quit the application. 73 | 74 | ## Example Usage 75 | 76 | Here's an example of a user session with the Shell Command Generator: 77 | 78 | ``` 79 | (venv) timkitch@TimsBeelinkPC:~/ai-projects/yt-ai-shell-command-generator$ python main.py 80 | 1. cmd 81 | 2. powershell 82 | 3. bash 83 | Select your preferred shell environment: 3 84 | Shell Command Generator initialized for bash. 85 | Enter your command query (or 'exit' to quit): give me a command to back up my home directory to an external drive 86 | 87 | Generated command for bash: 88 | rsync -avz --delete ~/. /path/to/external/drive/backup/ 89 | Do you want to copy this command to clipboard? [y/N]: N 90 | 91 | Command (for easy copy-paste): 92 | rsync -avz --delete ~/. /path/to/external/drive/backup/ 93 | 94 | Enter your command query (or 'exit' to quit): 95 | ``` 96 | 97 | This example demonstrates how the application prompts the user to select a shell environment, accepts a command query, generates a suitable command, and provides options for clipboard copying. 98 | 99 | ## Testing 100 | 101 | To run the unit tests: 102 | 103 | ``` 104 | python -m unittest test_main.py 105 | ``` 106 | 107 | ## Contributing 108 | 109 | Contributions are welcome! Please feel free to submit a Pull Request. 110 | 111 | ## License 112 | 113 | This project is open source and available under the [MIT License](LICENSE). This means you're free to use, modify, and distribute the code, even for commercial purposes, as long as you include the original copyright notice and license terms. 114 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import click 3 | import pyperclip 4 | from dotenv import load_dotenv 5 | from anthropic import Anthropic 6 | from click import style 7 | from anthropic.types import Completion 8 | 9 | load_dotenv() # Load environment variables from .env file 10 | 11 | def select_shell(shells=['cmd', 'powershell', 'bash'], input_func=click.prompt): 12 | """ 13 | Prompts the user to select their preferred shell environment from a list of options. 14 | 15 | Args: 16 | shells (list): List of available shells. 17 | input_func (function): Function to use for user input (for testing purposes). 18 | 19 | Returns: 20 | str: The selected shell environment. 21 | """ 22 | for i, shell in enumerate(shells, 1): 23 | click.echo(f"{i}. {shell}") 24 | while True: 25 | choice = input_func("Select your preferred shell environment") 26 | try: 27 | choice = int(choice) 28 | if 1 <= choice <= len(shells): 29 | return shells[choice - 1] 30 | except ValueError: 31 | pass 32 | click.echo("Invalid choice. Please try again.") 33 | 34 | def generate_command(client, shell, query) -> str: 35 | """ 36 | Generate a shell command based on a user's query using the Anthropic API. 37 | 38 | Args: 39 | client (Anthropic): An Anthropic API client instance. 40 | shell (str): The user's preferred shell environment. 41 | query (str): The user's command query. 42 | 43 | Returns: 44 | str: The generated shell command. 45 | """ 46 | response = client.messages.create( 47 | model="claude-3-5-sonnet-20241022", 48 | max_tokens=300, 49 | messages=[ 50 | { 51 | "role": "user", 52 | "content": f"Generate a valid {shell} command for the following query: {query}. Return ONLY the command, without any explanation." 53 | } 54 | ] 55 | ) 56 | # Extract the command from the response 57 | command = response.content[0].text.strip() 58 | # Ensure the command is not empty and doesn't start with explanatory text 59 | if not command or not command[0].isalnum(): 60 | command = f"echo 'Unable to generate a valid command for: {query}'" 61 | return command 62 | 63 | def copy_to_clipboard(text): 64 | pyperclip.copy(text) 65 | click.echo(style("Command copied to clipboard!", fg="green")) 66 | 67 | @click.command() 68 | def main(): 69 | shell = select_shell() 70 | api_key = os.getenv("ANTHROPIC_API_KEY") 71 | if not api_key: 72 | raise ValueError("ANTHROPIC_API_KEY not found in environment variables") 73 | client = Anthropic(api_key=api_key) 74 | 75 | click.echo(style(f"Shell Command Generator initialized for {shell}.", fg="green", bold=True)) 76 | 77 | while True: 78 | query = click.prompt(style("Enter your command query (or 'exit' to quit)", fg="cyan")) 79 | if query.lower() == 'exit': 80 | break 81 | command = generate_command(client, shell, query) 82 | click.echo(style(f"\nGenerated command for {shell}:", fg="yellow", bold=True)) 83 | click.echo(style(command, fg="green")) # Print the command with green styling 84 | 85 | if click.confirm(style("Do you want to copy this command to clipboard?", fg="cyan")): 86 | copy_to_clipboard(command) 87 | click.echo(style("Command copied to clipboard!", fg="green")) 88 | 89 | click.echo(style("\nCommand (for easy copy-paste):", fg="cyan")) 90 | click.echo(command) # Print the command again without styling for easy copy-paste 91 | 92 | click.echo() # Add an empty line for better readability 93 | 94 | click.echo(style("Exiting Shell Command Generator.", fg="red", bold=True)) 95 | 96 | if __name__ == "__main__": 97 | main() 98 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anthropic==0.44.0 2 | python-dotenv==1.0.0 3 | click==8.1.3 4 | pyperclip==1.8.2 5 | -------------------------------------------------------------------------------- /shell-command-generator-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timkitch/yt-ai-shell-command-generator/98b44027f3db570166602f0efa68f6b11925659e/shell-command-generator-sequence.png -------------------------------------------------------------------------------- /shell-command-generator-sequence.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | actor User 3 | participant "main()" as Main 4 | participant "select_shell()" as SelectShell 5 | participant "Anthropic API" as API 6 | participant "generate_command()" as GenerateCommand 7 | participant "copy_to_clipboard()" as Clipboard 8 | 9 | User -> Main: Run application 10 | activate Main 11 | 12 | Main -> SelectShell: Call select_shell() 13 | activate SelectShell 14 | User -> SelectShell: Choose shell 15 | SelectShell --> Main: Return selected shell 16 | deactivate SelectShell 17 | 18 | loop Until user exits 19 | User -> Main: Enter command query 20 | Main -> GenerateCommand: Call generate_command() 21 | activate GenerateCommand 22 | GenerateCommand -> API: Send query 23 | activate API 24 | API --> GenerateCommand: Return generated command 25 | deactivate API 26 | GenerateCommand --> Main: Return command and token count 27 | deactivate GenerateCommand 28 | 29 | Main -> User: Display generated command 30 | 31 | alt User chooses to copy 32 | User -> Main: Confirm copy to clipboard 33 | Main -> Clipboard: Call copy_to_clipboard() 34 | activate Clipboard 35 | Clipboard --> Main: Command copied 36 | deactivate Clipboard 37 | Main -> User: Confirm copy action 38 | end 39 | end 40 | 41 | User -> Main: Exit application 42 | Main -> User: Display total tokens used 43 | deactivate Main 44 | 45 | @enduml 46 | -------------------------------------------------------------------------------- /test_main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | from main import select_shell 4 | 5 | class TestSelectShell(unittest.TestCase): 6 | @patch('click.echo') 7 | def test_select_shell_valid_input(self, mock_echo): 8 | with patch('builtins.input', return_value='2'): 9 | result = select_shell(input_func=input) 10 | self.assertEqual(result, 'powershell') 11 | 12 | @patch('click.echo') 13 | def test_select_shell_invalid_then_valid_input(self, mock_echo): 14 | with patch('builtins.input', side_effect=['4', '1']): 15 | result = select_shell(input_func=input) 16 | self.assertEqual(result, 'cmd') 17 | 18 | @patch('click.echo') 19 | def test_select_shell_custom_shells(self, mock_echo): 20 | with patch('builtins.input', return_value='1'): 21 | result = select_shell(shells=['zsh', 'fish'], input_func=input) 22 | self.assertEqual(result, 'zsh') 23 | 24 | @patch('click.echo') 25 | def test_select_shell_non_numeric_input(self, mock_echo): 26 | with patch('builtins.input', side_effect=['abc', '2']): 27 | result = select_shell(input_func=input) 28 | self.assertEqual(result, 'powershell') 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | --------------------------------------------------------------------------------