commit c19fb93ec5c062f6e91724e08a23ed4196a2ebe4 Author: Heiko Joerg Schick Date: Wed Apr 9 09:34:15 2025 +0200 Initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..00075c1 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY="your-openai-api-key-here" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..86b2e3e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.bat linguist-detectable=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7904b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +env/ +ENV/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Logs +logs/ +*.log + +# Environment variables +.env + +# API keys +.openai.apikey + +# OS specific +.DS_Store +Thumbs.db diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e8689e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 wunderwuzzi23 +Greetings from Seattle! + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a65299 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# Edison - AI Command Assistant + +![Animated GIF](https://github.com/wunderwuzzi23/blog/raw/master/static/images/2023/yolo-shell-anim-gif.gif) + +## Overview + +Edison is an AI-powered command-line assistant that helps you interact with your terminal more efficiently. Just describe what you want to do in natural language, and Edison will generate and execute the appropriate command. + +``` +Edison v0.2 + +Usage: edison [options] +Arguments: + -s, --safety : Enable safety mode (only useful when safety is off) + -c, --config : Print current configuration + -v, --verbose : Enable verbose logging + -i, --interactive : Start interactive shell mode + -e, --explain : Explain the generated command + --no-streaming : Disable streaming output for command generation + --no-rich : Disable rich text formatting + --theme : Set syntax highlighting theme (e.g., monokai, github-dark) + +Current configuration per edison.yaml: +* Model : gpt-4o-mini +* Temperature : 0 +* Max. Tokens : 500 +* Safety : on +* Streaming : on +* Show Prefix : off + +UI Configuration: +* Rich Formatting : on +* Theme : monokai +* Command Style : panel +* Structured Expl.: on +``` + +## Features + +- Natural language processing for command generation +- Support for GPT-3.5 and GPT-4 models +- Real-time streaming of command generation +- Rich text formatting with syntax highlighting +- Structured command explanations +- Direct command modification (edit commands before execution) +- Configurable settings via `edison.yaml` +- Cross-platform support (Linux, macOS, Windows) +- Safety mode to confirm commands before execution +- Interactive shell mode with command history + +## Installation + +### Linux and macOS + +```bash +git clone https://github.com/user/command-assistant +cd command-assistant +source install_edison.sh +``` + +### Windows + +```powershell +git clone https://github.com/user/command-assistant +cd command-assistant +.\install_edison.bat +``` + +## OpenAI API Key Configuration + +Configure your OpenAI API key using one of these methods: + +- Environment variable: `export OPENAI_API_KEY=` (Linux/macOS) or `$env:OPENAI_API_KEY=""` (Windows) +- Create a file at `~/.openai.apikey` with just the key +- Add the key to the `edison.yaml` configuration file + +## Usage Examples + +``` +edison what's the time? +edison show me some unicode characters +edison what is my username and machine name? +edison is there a process called chrome running? +edison download the homepage of example.com and save it to index.html +edison find all unique URLs in index.html +edison create a file named test.txt with my username +edison -a delete the test.txt file +edison show me the current price of Bitcoin in USD +edison check the SSH logs for suspicious logins +``` + +## Safety Mode + +By default, Edison will prompt for confirmation before executing commands. You can configure this behavior in `edison.yaml`. + +## Streaming Mode + +Edison displays command generation in real-time as tokens arrive from the API, providing immediate feedback. You can: + +- Enable/disable streaming in `edison.yaml` with the `streaming` option +- Disable streaming for a single command with the `--no-streaming` flag +- Cancel a streaming command generation with Ctrl+C +- Hide the "Generating command: " prefix with the `show_generating_prefix: false` option in `edison.yaml` + +## Command Modification + +Edison allows you to modify commands in two ways: + +1. **Modify the prompt**: Change the natural language description to generate a new command +2. **Modify the command directly**: Edit the generated command before execution + +When you select the "modify" option (by typing `m`), Edison will ask whether you want to modify the prompt or the command: + +``` +Execute command? [Y]es [n]o [m]odify (prompt/command) [c]opy to clipboard ==> m +Modify [p]rompt or [c]ommand? [c] ==> +``` + +- Choose `p` to modify the prompt and generate a new command +- Choose `c` (default) to directly edit the generated command + +## Rich Formatting + +Edison provides enhanced terminal output with rich text formatting: + +- Syntax highlighting for generated commands +- Structured command explanations with better formatting +- Customizable themes for syntax highlighting +- Panel-based UI elements for better visual organization + +You can configure rich formatting options in `edison.yaml` under the `ui` section: + +```yaml +ui: + rich_formatting: true + theme: "monokai" + command_style: "panel" + structured_explanations: true +``` + +Or disable rich formatting for a single command with the `--no-rich` flag. + +## License + +MIT. No Liability. No Warranty. diff --git a/custom_theme_demo.py b/custom_theme_demo.py new file mode 100644 index 0000000..6fb8905 --- /dev/null +++ b/custom_theme_demo.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +""" +Custom theme demonstration for Edison. +This script shows how to create and use a custom theme with Edison. +""" +import sys +import os +from pygments.style import Style +from pygments.token import ( + Comment, Error, Keyword, Literal, Name, Number, Operator, String, Text +) +from pygments.styles import STYLE_MAP +from rich.console import Console +from rich.syntax import Syntax +from rich.panel import Panel + +# Add the parent directory to the path so we can import Edison modules +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Import Edison's markdown_utils module +from edison.utils.markdown_utils import print_command_rich + +# Sample command to demonstrate syntax highlighting +SAMPLE_COMMAND = """#!/bin/bash + +# This is a comment +echo "Hello, world!" + +# Define variables +name="User" +count=10 + +# Conditional statement +if [ "$count" -gt 5 ]; then + echo "Count is greater than 5" + for ((i=0; i<$count; i++)); do + echo "Processing item $i for $name" + done +fi + +# Function definition +function process_files() { + local dir="$1" + find "$dir" -type f -name "*.txt" +} + +# Call the function +process_files "/var/log" +""" + +# Define custom themes +class EdisonDarkTheme(Style): + """A custom dark theme for Edison.""" + + # Define colors + background_color = "#1e1e2e" # Dark background + highlight_color = "#313244" + + # Define token colors + styles = { + Text: "#cdd6f4", # Light gray for regular text + Error: "#f38ba8", # Red for errors + Comment: "#6c7086", # Muted gray for comments + Keyword: "#cba6f7", # Purple for keywords + Keyword.Reserved: "#cba6f7", + Keyword.Namespace: "#cba6f7", + Name: "#cdd6f4", # Light gray for names + Name.Builtin: "#f9e2af", # Yellow for builtins + Name.Function: "#89b4fa", # Blue for functions + Name.Class: "#f9e2af", # Yellow for classes + Name.Decorator: "#89b4fa", # Blue for decorators + Name.Variable: "#f5c2e7", # Pink for variables + Number: "#fab387", # Orange for numbers + Operator: "#94e2d5", # Teal for operators + String: "#a6e3a1", # Green for strings + } + +class EdisonLightTheme(Style): + """A custom light theme for Edison.""" + + # Define colors + background_color = "#fafafa" # Light background + highlight_color = "#e6e6e6" + + # Define token colors + styles = { + Text: "#383a42", # Dark gray for regular text + Error: "#e45649", # Red for errors + Comment: "#a0a1a7", # Gray for comments + Keyword: "#a626a4", # Purple for keywords + Keyword.Reserved: "#a626a4", + Keyword.Namespace: "#a626a4", + Name: "#383a42", # Dark gray for names + Name.Builtin: "#c18401", # Yellow for builtins + Name.Function: "#4078f2", # Blue for functions + Name.Class: "#c18401", # Yellow for classes + Name.Decorator: "#4078f2", # Blue for decorators + Name.Variable: "#e45649", # Red for variables + Number: "#986801", # Brown for numbers + Operator: "#0184bc", # Teal for operators + String: "#50a14f", # Green for strings + } + +def register_custom_themes(): + """Register custom themes with Pygments.""" + STYLE_MAP["edison-dark"] = "custom_theme_demo.EdisonDarkTheme" + STYLE_MAP["edison-light"] = "custom_theme_demo.EdisonLightTheme" + +def main(): + """Main function to demonstrate custom themes.""" + console = Console() + + # Register custom themes + register_custom_themes() + + # Print header + console.print("\n[bold cyan]Edison Custom Theme Demonstration[/bold cyan]") + console.print("This shows how to create and use custom themes with Edison.\n") + + # Display built-in theme for comparison + console.print("[bold]Built-in Theme (monokai):[/bold]") + print_command_rich(SAMPLE_COMMAND, theme="monokai") + console.print() + + # Display custom themes + console.print("[bold]Custom Theme (edison-dark):[/bold]") + print_command_rich(SAMPLE_COMMAND, theme="edison-dark") + console.print() + + console.print("[bold]Custom Theme (edison-light):[/bold]") + print_command_rich(SAMPLE_COMMAND, theme="edison-light") + console.print() + + # Show how to create and use custom themes + console.print("[bold]How to Create and Use Custom Themes:[/bold]") + console.print(""" +1. Create a custom theme class that inherits from pygments.style.Style: + +```python +from pygments.style import Style +from pygments.token import Comment, Keyword, Name, Number, Operator, String, Text + +class MyCustomTheme(Style): + background_color = "#1e1e2e" + highlight_color = "#313244" + + styles = { + Text: "#cdd6f4", + Comment: "#6c7086", + Keyword: "#cba6f7", + Name.Function: "#89b4fa", + Name.Variable: "#f5c2e7", + Number: "#fab387", + Operator: "#94e2d5", + String: "#a6e3a1", + } +``` + +2. Register your theme with Pygments: + +```python +from pygments.styles import STYLE_MAP +STYLE_MAP["my-theme"] = "module_name.MyCustomTheme" +``` + +3. Use your custom theme with Edison: + +```python +# In your code +from edison.utils.markdown_utils import print_command_rich +print_command_rich("ls -la", theme="my-theme") + +# Or via command line +edison --theme my-theme "list files" +``` + +4. To make your theme permanently available, create a Python package and install it: + +```python +# setup.py +from setuptools import setup + +setup( + name="edison-custom-themes", + version="0.1.0", + py_modules=["edison_themes"], + entry_points={ + "pygments.styles": [ + "my-theme = edison_themes:MyCustomTheme", + ], + }, +) +``` + +Then install with: pip install -e . + """) + +if __name__ == "__main__": + main() diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b89d02d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,29 @@ +# Edison Documentation + +Welcome to the Edison documentation! This documentation is designed to help users and developers understand how to use and extend Edison. + +## Documentation Overview + +The documentation is organized into two main sections: + +- **[User Documentation](user/README.md)**: For end users who want to learn how to use Edison effectively. +- **[Developer Documentation](developer/README.md)**: For developers who want to understand Edison's architecture and how to extend it. + +## What is Edison? + +Edison is an AI-powered command line assistant that translates natural language into shell commands. It helps users interact with their terminal more effectively by allowing them to describe what they want to do in plain English. + +``` +$ edison how to find large files in the current directory +Command: find . -type f -size +10M -exec ls -lh {} \; | sort -k5,5hr + +Execute command? [Y]es [n]o [m]odify [c]opy to clipboard ==> +``` + +## Quick Links + +- [Installation Guide](user/installation.md) +- [Basic Usage](user/basic-usage.md) +- [Advanced Features](user/advanced-features.md) +- [Architecture Overview](developer/architecture.md) +- [Contribution Guide](developer/contributing.md) \ No newline at end of file diff --git a/docs/developer/README.md b/docs/developer/README.md new file mode 100644 index 0000000..fb4360a --- /dev/null +++ b/docs/developer/README.md @@ -0,0 +1,84 @@ +# Edison Developer Documentation + +Welcome to the Edison developer documentation! This guide will help you understand Edison's architecture, code organization, and how to extend its functionality. + +## Table of Contents + +1. [Architecture Overview](architecture.md) +2. [Code Structure](code-structure.md) +3. [API Integration](api-integration.md) +4. [Contributing Guidelines](contributing.md) +5. [Development Environment Setup](development-setup.md) + +## Getting Started with Development + +Edison is a Python application that translates natural language into shell commands using OpenAI's APIs. It's designed to be modular and extensible, making it easy to add new features. + +```mermaid +flowchart TD + User([User]) -->|Query| CLI[CLI Module] + CLI -->|Process| Core[Core Module] + Core -->|API Request| OpenAI[OpenAI API] + OpenAI -->|Response| Core + Core -->|Command| Shell[Shell] + Core -->|Feedback| CLI + CLI -->|Display| User + + subgraph Edison Application + CLI + Core + end + + style User fill:#f9d5e5,stroke:#333,stroke-width:2px + style Edison Application fill:#d3f6db,stroke:#333,stroke-width:4px + style OpenAI fill:#d3f6f5,stroke:#333,stroke-width:2px + style Shell fill:#eeeeee,stroke:#333,stroke-width:2px +``` + +## Quick Start for Developers + +1. **Clone the repository**: + ```bash + git clone https://github.com/user/command-assistant + cd command-assistant + ``` + +2. **Create a development environment**: + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + pip install -e . + ``` + +3. **Run Edison in development mode**: + ```bash + python -m edison your query here + ``` + +## Key Developer Resources + +| Resource | Description | +|----------|-------------| +| [Architecture Overview](architecture.md) | High-level design and component interaction | +| [Code Structure](code-structure.md) | Detailed breakdown of the codebase | +| [API Integration](api-integration.md) | How Edison interacts with the OpenAI API | +| [Contributing Guidelines](contributing.md) | How to contribute to the project | + +## Development Philosophy + +Edison follows these design principles: + +1. **Modularity**: Components should be loosely coupled and focused on a single responsibility +2. **Simplicity**: Keep the codebase simple and maintainable +3. **User-centric**: Features should address real user needs +4. **Safety**: Prioritize user safety when generating and executing commands + +## Component Overview + +Edison is organized into several main components: + +- **CLI**: Command-line interface and user interaction +- **Core**: Business logic, API integration, and command processing +- **Config**: Configuration management +- **UI**: User interface components +- **Utils**: Utility functions and helpers \ No newline at end of file diff --git a/docs/developer/api-integration.md b/docs/developer/api-integration.md new file mode 100644 index 0000000..b8ae222 --- /dev/null +++ b/docs/developer/api-integration.md @@ -0,0 +1,312 @@ +# API Integration + +This document explains how Edison integrates with the OpenAI API to translate natural language into shell commands. + +## Overview + +```mermaid +flowchart LR + A[User Query] --> B[Prompt Construction] + B --> C[API Request] + C --> D[Response Processing] + D --> E[Command Extraction] + E --> F[Command Validation] + F --> G[Command Execution] + + style A fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style C fill:#d3f6db,stroke:#333,stroke-width:2px + style D fill:#d3f6f5,stroke:#333,stroke-width:2px + style E fill:#d5f6d5,stroke:#333,stroke-width:2px + style F fill:#f6f6d5,stroke:#333,stroke-width:2px + style G fill:#f5d5f5,stroke:#333,stroke-width:2px +``` + +## API Client Module + +The `api_client.py` module is responsible for all OpenAI API interactions: + +```python +def get_api_key(config): + """Get the OpenAI API key from various sources.""" + # Find API key from environment, file, or config + +def create_client(config): + """Create and initialize an OpenAI client.""" + # Initialize client with API key + +def call_api(client, config, query): + """Call the OpenAI API with the given query.""" + # Send request to API and extract response + +def generate_command(client, config, query, max_retries=3): + """Generate a command using the OpenAI API with retry logic.""" + # Call API with retries for rate limits +``` + +## API Key Management + +Edison supports multiple methods for supplying the OpenAI API key, processed in this order: + +1. **Environment Variable**: `OPENAI_API_KEY` +2. **API Key File**: `~/.openai.apikey` +3. **Configuration File**: `openai_api_key` in `edison.yaml` + +This implementation is in `get_api_key()`: + +```python +def get_api_key(config): + """Get the OpenAI API key from various sources.""" + dotenv.load_dotenv() + + # Method 1: Environment variable + api_key = os.getenv("OPENAI_API_KEY") + + # Method 2: File in home directory + if not api_key: + home_path = os.path.expanduser("~") + api_key_path = os.path.join(home_path, ".openai.apikey") + if os.path.exists(api_key_path): + with open(api_key_path, 'r') as f: + api_key = f.read().strip() + + # Method 3: Configuration file + if not api_key: + api_key = config.get("openai_api_key") + + if not api_key: + raise ValueError("No OpenAI API key found") + + return api_key +``` + +## Prompt Construction + +Edison uses a template-based approach to construct effective prompts: + +```mermaid +sequenceDiagram + participant User + participant PromptManager + participant Template + participant APIClient + participant OpenAI + + User->>APIClient: Query: "list all files" + APIClient->>PromptManager: get_full_prompt(query, shell) + PromptManager->>Template: Load template + Template-->>PromptManager: Template content + PromptManager->>PromptManager: Format template with query + PromptManager-->>APIClient: Formatted prompt + APIClient->>OpenAI: Send API request + OpenAI-->>APIClient: Command response +``` + +The `prompt_manager.py` module handles this: + +```python +def load_prompt_template(shell="bash"): + """Load the prompt template with shell-specific considerations.""" + # Load and return the appropriate template + +def get_full_prompt(query, shell="bash"): + """Get the full prompt for the given query and shell.""" + # Format prompt with query and shell +``` + +### Prompt Template + +Edison uses a prompt template (`edison.prompt`) to structure requests to the AI model. The template: + +1. Provides context about the desired output format +2. Includes examples of good responses +3. Specifies the shell environment +4. Encourages safe commands +5. Includes the user's query + +## API Request + +Edison uses the OpenAI Python client library for API requests: + +```python +def call_api(client, config, query): + """Call the OpenAI API with the given query.""" + prompt = prompt_manager.get_full_prompt(query, config.get("shell", "bash")) + system_prompt = prompt.split('\n')[0] if '\n' in prompt else prompt + + response = client.chat.completions.create( + model=config.get("model", "gpt-3.5-turbo"), + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt} + ], + temperature=config.get("temperature", 0), + max_tokens=config.get("max_tokens", 500), + ) + + return response.choices[0].message.content.strip() +``` + +### Streaming API Integration + +Edison also supports streaming command generation, which provides a more responsive user experience: + +```python +def call_api_streaming(client, config, query, callback): + """Call the OpenAI API with streaming enabled. + + Args: + client: The OpenAI client + config: The configuration dictionary + query: The user query + callback: Function to call with each token + """ + prompt = prompt_manager.get_full_prompt(query, config.get("shell", "bash")) + system_prompt = prompt.split('\n')[0] if '\n' in prompt else prompt + + response = client.chat.completions.create( + model=config.get("model", "gpt-3.5-turbo"), + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt} + ], + temperature=config.get("temperature", 0), + max_tokens=config.get("max_tokens", 500), + stream=True # Enable streaming + ) + + # Collect the full response while calling the callback for each chunk + full_response = "" + + for chunk in response: + if chunk.choices and chunk.choices[0].delta.content: + content = chunk.choices[0].delta.content + full_response += content + callback(content) # Call the callback with each chunk + + return full_response.strip() +``` + +This streaming functionality is integrated with the UI through a callback function that updates the display in real-time as tokens are received. + +### Request Parameters + +The API request includes these key parameters: + +| Parameter | Description | Default | +|-----------|-------------|---------| +| model | The OpenAI model to use | gpt-3.5-turbo | +| temperature | Randomness of completions (0-1) | 0 | +| max_tokens | Maximum tokens in response | 500 | + +## Error Handling and Retries + +The `generate_command()` function implements retry logic to handle rate limiting: + +```python +def generate_command(client, config, query, max_retries=3): + """Generate a command using the OpenAI API with retry logic.""" + for attempt in range(max_retries): + try: + return call_api(client, config, query) + except Exception as e: + if "rate limit" in str(e).lower() and attempt < max_retries - 1: + wait_time = 2 ** attempt # Exponential backoff + logger.warning(f"Rate limited. Retrying in {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error(f"Error after {attempt+1} attempts: {str(e)}") + raise +``` + +Key features: +- Exponential backoff for rate limits +- Maximum retry attempts +- Detailed error logging + +## Command Processing and Validation + +After receiving the API response, Edison: + +1. Extracts the command from the response +2. Validates the command for safety +3. Checks for markdown or other formatting issues +4. Prepares the command for execution + +## Extending the API Integration + +To support additional AI providers or models: + +1. **Create a new client factory function**: + ```python + def create_anthropic_client(config): + # Initialize Anthropic client + ``` + +2. **Add a model selection mechanism**: + ```python + def get_client_for_model(config): + model = config.get("model", "gpt-3.5-turbo") + if model.startswith("claude"): + return create_anthropic_client(config) + else: + return create_client(config) + ``` + +3. **Implement provider-specific API call function**: + ```python + def call_anthropic_api(client, config, query): + # Format request for Anthropic API + ``` + +4. **Update the command generation logic**: + ```python + def generate_command(client, config, query, max_retries=3): + model = config.get("model", "gpt-3.5-turbo") + if model.startswith("claude"): + return call_anthropic_api(client, config, query) + else: + return call_api(client, config, query) + ``` + +## API Response Examples + +### Successful Response + +```json +{ + "choices": [ + { + "message": { + "content": "ls -la", + "role": "assistant" + }, + "index": 0, + "finish_reason": "stop" + } + ] +} +``` + +### Error Response + +```json +{ + "error": { + "message": "Rate limit exceeded", + "type": "rate_limit_error", + "param": null, + "code": null + } +} +``` + +## Performance Considerations + +To optimize API usage and performance: + +1. **Use Efficient Models**: Default to `gpt-3.5-turbo` for lower latency +2. **Limit Token Usage**: Keep max_tokens reasonable (default 500) +3. **Request Caching**: Consider implementing caching for common queries +4. **Concurrent Requests**: For batch processing, consider async requests +5. **Prompt Optimization**: Keep prompt templates concise but effective \ No newline at end of file diff --git a/docs/developer/architecture.md b/docs/developer/architecture.md new file mode 100644 index 0000000..a044d8d --- /dev/null +++ b/docs/developer/architecture.md @@ -0,0 +1,177 @@ +# Architecture Overview + +This document provides a comprehensive overview of Edison's architecture, components, and their interactions. + +## System Architecture + +```mermaid +graph TD + subgraph User Interaction + CLI[CLI Module] + Interactive[Interactive Mode] + Console[Console UI] + end + + subgraph Core Components + APIClient[API Client] + PromptManager[Prompt Manager] + CommandExecutor[Command Executor] + end + + subgraph Configuration + ConfigManager[Config Manager] + Validation[Validation] + end + + subgraph Utilities + Logging[Logging Utils] + OSUtils[OS Utils] + end + + CLI --> APIClient + CLI --> CommandExecutor + CLI --> Console + CLI --> Interactive + Interactive --> APIClient + Interactive --> CommandExecutor + + APIClient --> PromptManager + APIClient --> ConfigManager + CommandExecutor --> Validation + CommandExecutor --> OSUtils + + ConfigManager --> Logging + + style User Interaction fill:#f9d5e5,stroke:#333,stroke-width:2px + style Core Components fill:#d3f6db,stroke:#333,stroke-width:2px + style Configuration fill:#d3f6f5,stroke:#333,stroke-width:2px + style Utilities fill:#eeeeee,stroke:#333,stroke-width:2px +``` + +## Component Breakdown + +### User Interaction Layer + +The user interaction layer handles all user-facing functionality: + +1. **CLI Module** (`edison/cli.py`): + - Entry point for the application + - Parses command-line arguments + - Initializes logging and configuration + - Coordinates between components + +2. **Interactive Mode** (`edison/ui/interactive.py`): + - Provides a dedicated shell for continuous interaction + - Manages command history and user input + - Handles special commands and actions + +3. **Console UI** (`edison/ui/console.py`): + - Formats and displays text output + - Presents commands and prompt options + - Manages colored output with termcolor + +### Core Components + +The core components handle the main business logic: + +1. **API Client** (`edison/core/api_client.py`): + - Manages communication with the OpenAI API + - Handles API key management and retry logic + - Processes responses from the API + +2. **Prompt Manager** (`edison/core/prompt_manager.py`): + - Constructs effective prompts for the AI model + - Formats user queries for optimal command generation + - Adapts prompts based on shell and OS + +3. **Command Executor** (`edison/core/command_executor.py`): + - Safely executes generated commands + - Handles command output and errors + - Adapts execution for different shells and platforms + +### Configuration Layer + +The configuration layer manages settings and validation: + +1. **Config Manager** (`edison/config/config_manager.py`): + - Loads and validates configuration from edison.yaml + - Provides default settings + - Handles environment variables for API keys + +2. **Validation** (`edison/utils/validation.py`): + - Validates commands for safety + - Detects potentially dangerous operations + - Checks for invalid syntax or formatting + +### Utilities + +Utility modules provide support functions: + +1. **Logging Utils** (`edison/utils/logging_utils.py`): + - Configures logging for the application + - Manages log files and levels + - Provides logger access + +2. **OS Utils** (`edison/utils/os_utils.py`): + - Provides OS-specific functionality + - Detects platform and shell information + - Manages environment variables + +## Data Flow + +```mermaid +sequenceDiagram + participant User + participant CLI + participant Config + participant APIClient + participant CommandExecutor + participant Shell + + User->>CLI: Input query + CLI->>Config: Load configuration + CLI->>APIClient: Send query + APIClient->>APIClient: Format prompt + APIClient->>OpenAI: API request + OpenAI-->>APIClient: Command response + APIClient-->>CLI: Return command + CLI->>User: Display command + User->>CLI: Confirm execution + CLI->>CommandExecutor: Execute command + CommandExecutor->>Shell: Run in shell + Shell-->>CommandExecutor: Command output + CommandExecutor-->>CLI: Execution result + CLI->>User: Display output +``` + +1. User inputs a natural language query +2. CLI loads configuration and processes arguments +3. Query is sent to the API client +4. API client formats the prompt and sends it to OpenAI +5. Response is received and processed +6. Command is displayed to the user +7. User confirms, modifies, or cancels execution +8. If confirmed, command is sent to the command executor +9. Command executor runs the command in the appropriate shell +10. Output is returned to the CLI and displayed to the user + +## Extension Points + +Edison is designed to be extensible in several ways: + +1. **New UI Components**: Add new UI modes by extending the UI components +2. **Alternative AI Providers**: The API client can be extended to support other AI providers +3. **Custom Prompt Templates**: The prompt manager can be enhanced with specialized prompts +4. **Additional Command Validation**: Add custom validation rules in the validation module +5. **Platform-Specific Features**: Extend OS utils for additional platform support + +## Technologies Used + +Edison is built using: + +- **Python**: Core programming language +- **OpenAI API**: For natural language processing and command generation +- **prompt_toolkit**: For interactive shell functionality +- **termcolor**: For colored terminal output +- **pyyaml**: For configuration file parsing +- **pyperclip**: For clipboard integration \ No newline at end of file diff --git a/docs/developer/code-structure.md b/docs/developer/code-structure.md new file mode 100644 index 0000000..ae45ecd --- /dev/null +++ b/docs/developer/code-structure.md @@ -0,0 +1,324 @@ +# Code Structure + +This document provides a detailed overview of Edison's code organization and file structure. + +## Directory Structure + +``` +edison/ +├── __init__.py # Package initialization +├── __main__.py # Entry point for module execution +├── cli.py # Command-line interface +├── edison.prompt # Prompt template for command generation +├── edison.yaml # Default configuration file +├── config/ # Configuration management +│ ├── __init__.py +│ └── config_manager.py +├── core/ # Core business logic +│ ├── __init__.py +│ ├── api_client.py # OpenAI API integration +│ ├── command_executor.py # Command execution +│ └── prompt_manager.py # Prompt handling +├── logs/ # Log files directory +├── ui/ # User interface components +│ ├── __init__.py +│ ├── console.py # Console output formatting +│ └── interactive.py # Interactive shell mode +└── utils/ # Utility functions + ├── __init__.py + ├── logging_utils.py # Logging configuration + ├── os_utils.py # OS-specific utilities + └── validation.py # Command validation +``` + +## Key Files and Their Functions + +```mermaid +classDiagram + cli <|-- config_manager + cli <|-- api_client + cli <|-- console + cli <|-- interactive + api_client <|-- prompt_manager + interactive <|-- api_client + interactive <|-- command_executor + + class cli { + +main() + +parse_arguments() + } + + class config_manager { + +load_config() + +print_config() + } + + class api_client { + +create_client() + +generate_command() + +call_api() + +get_api_key() + } + + class prompt_manager { + +get_full_prompt() + +load_prompt_template() + } + + class command_executor { + +execute_command() + } + + class console { + +print_command() + +prompt_user_input() + +handle_command_execution() + +handle_user_input() + } + + class interactive { + +interactive_mode() + +get_command_explanation() + +show_help() + } + + class logging_utils { + +setup_logging() + +get_logger() + } + + class os_utils { + +get_default_shell() + +missing_posix_display() + } + + class validation { + +check_for_issue() + +check_for_markdown() + +is_dangerous_command() + } +``` + +### Core Modules + +#### cli.py + +The main entry point and command-line interface for Edison: +- Parses command-line arguments +- Initializes logging +- Loads configuration +- Handles user interaction flow +- Manages error handling + +```python +def main(): + """Main entry point for the application.""" + # Parse arguments, set up logging, load config, etc. + +def parse_arguments(): + """Parse command line arguments.""" + # Define and parse command-line arguments +``` + +#### config/config_manager.py + +Handles configuration loading and management: +- Loads settings from edison.yaml +- Provides default values +- Validates configuration + +```python +def load_config(): + """Load configuration from the YAML file.""" + # Find and load the configuration file + +def print_config(config): + """Print the current configuration.""" + # Display configuration settings +``` + +#### core/api_client.py + +Manages interaction with the OpenAI API: +- Creates and initializes the API client +- Handles API key management +- Formats queries and processes responses +- Implements retry logic +- Supports both streaming and non-streaming command generation + +```python +def create_client(config): + """Create and initialize an OpenAI client.""" + # Initialize API client with key + +def generate_command(client, config, query, max_retries=3): + """Generate a command using the OpenAI API with retry logic.""" + # Send query to API and handle response + +def generate_command_streaming(client, config, query, callback): + """Generate a command using the OpenAI API with streaming output. + + Args: + client: The OpenAI client + config: The configuration dictionary + query: The user query + callback: Function to call with each token + """ + # Stream response tokens and call the callback for each one +``` + +#### core/command_executor.py + +Executes shell commands: +- Runs commands in the appropriate shell +- Handles command output and errors +- Implements safety checks + +```python +def execute_command(shell, command): + """Execute a shell command.""" + # Run command and handle result +``` + +#### core/prompt_manager.py + +Manages prompt templates and formatting: +- Loads prompt templates +- Formats user queries for optimal results +- Adapts prompts based on shell and OS + +```python +def get_full_prompt(query, shell="bash"): + """Get the full prompt for the given query and shell.""" + # Format prompt with query and shell info +``` + +### UI Modules + +#### ui/console.py + +Handles console output and user interaction: +- Formats and displays text +- Presents command options +- Handles user input +- Supports direct command modification +- Controls display of generating prefix + +```python +def print_command(command, show_generating_prefix=True): + """Print a command to the console.""" + # Display command with formatting, optionally showing the generating prefix + +def handle_command_execution(client, config, command, explain=False): + """Handle the execution of a command.""" + # Manage command execution flow + +def handle_direct_modification(command): + """Handle direct modification of a command. + + Args: + command: The command to modify + + Returns: + The modified command + """ + # Present interactive editor for direct command modification +``` + +#### ui/interactive.py + +Provides an interactive shell mode: +- Manages continuous interaction +- Handles command history +- Processes special commands + +```python +def interactive_mode(client, config): + """Start an interactive shell.""" + # Run interactive loop + +def get_command_explanation(client, command): + """Get an explanation for a command.""" + # Generate explanation +``` + +### Utility Modules + +#### utils/logging_utils.py + +Configures and manages logging: +- Sets up log files and formats +- Controls log levels +- Provides access to loggers + +```python +def setup_logging(verbose=False): + """Configure logging for the application.""" + # Set up logging with appropriate levels +``` + +#### utils/os_utils.py + +Provides OS-specific functionality: +- Detects platform information +- Manages environment-specific behavior +- Determines default shell + +```python +def get_default_shell(): + """Get the default shell for the current OS.""" + # Determine system shell +``` + +#### utils/validation.py + +Validates and checks commands: +- Detects potentially dangerous commands +- Checks for markdown formatting +- Identifies command issues + +```python +def is_dangerous_command(command): + """Check if a command is potentially dangerous.""" + # Analyze command for dangerous patterns +``` + +## Static Files + +- **edison.prompt**: Template for generating prompts sent to the OpenAI API +- **edison.yaml**: Default configuration file with settings + +## Dependencies and Imports + +The project uses these key dependencies: + +1. **Core Python Libraries**: + - `argparse`: For command-line argument parsing + - `logging`: For log management + - `subprocess`: For command execution + - `os`, `sys`: For system interaction + +2. **External Libraries**: + - `openai`: For API interaction + - `termcolor`: For colored terminal output + - `python-dotenv`: For environment variable loading + - `pyyaml`: For configuration parsing + - `pyperclip`: For clipboard operations + - `prompt_toolkit`: For interactive shell functionality + - `colorama`: For cross-platform color support + +## Code Style and Conventions + +Edison follows these coding conventions: + +1. **PEP 8**: Standard Python style guide +2. **Docstrings**: All functions and classes have docstrings in the Google style +3. **Error Handling**: Comprehensive try/except blocks with detailed logging +4. **Modularity**: Functions and classes have single responsibilities +5. **Type Hints**: Not currently used but planned for future versions + +## Special Considerations + +1. **Cross-Platform Support**: Code includes handling for different operating systems +2. **API Key Security**: Multiple secure methods for handling API keys +3. **Command Safety**: Validation to prevent dangerous command execution \ No newline at end of file diff --git a/docs/developer/contributing.md b/docs/developer/contributing.md new file mode 100644 index 0000000..8074fce --- /dev/null +++ b/docs/developer/contributing.md @@ -0,0 +1,208 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to Edison! This guide will help you get started with contributing to the project. + +## Development Workflow + +```mermaid +gitGraph + commit id: "Initial setup" + branch feature/my-feature + checkout feature/my-feature + commit id: "Implement feature" + commit id: "Add tests" + commit id: "Fix bugs" + checkout main + merge feature/my-feature + commit id: "Release v1.0.1" +``` + +## Getting Started + +### Setting Up Your Development Environment + +1. **Fork the repository**: + - Visit the GitHub repository and click the "Fork" button + +2. **Clone your fork**: + ```bash + git clone https://github.com/YOUR_USERNAME/command-assistant.git + cd command-assistant + ``` + +3. **Set up a virtual environment**: + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +4. **Install in development mode**: + ```bash + pip install -e . + ``` + +5. **Install development dependencies**: + ```bash + pip install pylint pytest + ``` + +### Development Process + +1. **Create a branch**: + ```bash + git checkout -b feature/my-feature-name + ``` + +2. **Make your changes**: + - Write code + - Add tests + - Update documentation + +3. **Run tests and linting**: + ```bash + # Run tests (future addition) + pytest + + # Run linting + pylint edison + ``` + +4. **Commit your changes**: + ```bash + git add . + git commit -m "Descriptive commit message" + ``` + +5. **Push to your fork**: + ```bash + git push origin feature/my-feature-name + ``` + +6. **Submit a pull request**: + - Go to your fork on GitHub + - Click "New Pull Request" + - Select your branch and submit + +## Code Style and Guidelines + +### Python Style Guide + +Edison follows [PEP 8](https://www.python.org/dev/peps/pep-0008/) with some additional guidelines: + +- Use 4 spaces for indentation +- Maximum line length of 100 characters +- Use meaningful variable and function names +- Write docstrings for all classes and functions + +### Docstring Format + +We use Google-style docstrings: + +```python +def example_function(param1, param2): + """ + Brief description of the function. + + Args: + param1: Description of param1 + param2: Description of param2 + + Returns: + Description of return value + + Raises: + ExceptionType: When and why this exception is raised + """ + pass +``` + +### Code Organization + +- Keep functions focused on a single responsibility +- Group related functionality in modules +- Use appropriate error handling +- Add useful log messages + +## Adding New Features + +When adding new features: + +1. **Start by discussing**: Open an issue to discuss your proposed feature +2. **Design the interface**: How will users interact with your feature? +3. **Plan the implementation**: Sketch out the implementation before coding +4. **Write tests**: Add tests to verify your feature works correctly +5. **Document the feature**: Update or add documentation explaining the feature +6. **Submit for review**: Submit a pull request for review + +## Release Process + +```mermaid +flowchart TD + A[Feature Development] --> B[Testing] + B --> C[Code Review] + C --> D{Approved?} + D -->|No| B + D -->|Yes| E[Merge to Main] + E --> F[Version Bump] + F --> G[Release] + + style A fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style C fill:#d3f6db,stroke:#333,stroke-width:2px + style D fill:#d3f6f5,stroke:#333,stroke-width:2px + style E fill:#f5d5f5,stroke:#333,stroke-width:2px + style F fill:#f5d5d5,stroke:#333,stroke-width:2px + style G fill:#d5d5f5,stroke:#333,stroke-width:2px +``` + +Edison follows semantic versioning (MAJOR.MINOR.PATCH): + +- **MAJOR**: Incompatible API changes +- **MINOR**: Backwards-compatible new features +- **PATCH**: Backwards-compatible bug fixes + +## Future Development Areas + +We're looking for contributions in these areas: + +1. **Testing Framework**: Adding a comprehensive test suite +2. **Documentation**: Expanding and improving documentation +3. **Supported Shells**: Adding support for additional shells +4. **Platform Support**: Enhancing cross-platform compatibility +5. **Model Support**: Adding support for alternative AI models +6. **Command Validation**: Improving safety checks +7. **Interactive Features**: Enhancing the interactive shell +8. **Performance Optimization**: Improving response times + +## Best Practices + +### Security Considerations + +- Never commit API keys or sensitive information +- Validate user input thoroughly +- Use safe subprocess execution methods +- Be careful with permissions and file operations + +### Performance + +- Minimize API calls where possible +- Use efficient algorithms and data structures +- Avoid unnecessary file I/O +- Profile code to identify bottlenecks + +### User Experience + +- Provide clear feedback to users +- Use consistent command-line interfaces +- Add helpful error messages +- Consider accessibility in your design + +## Getting Help + +If you need help with contributing: + +- Open an issue on GitHub +- Check existing documentation +- Reach out to maintainers + +Thank you for contributing to Edison! \ No newline at end of file diff --git a/docs/developer/development-setup.md b/docs/developer/development-setup.md new file mode 100644 index 0000000..62af11d --- /dev/null +++ b/docs/developer/development-setup.md @@ -0,0 +1,247 @@ +# Development Environment Setup + +This guide explains how to set up your development environment for Edison. + +## Prerequisites + +Before beginning development on Edison, ensure you have: + +- **Python 3.6+**: Required for core development +- **Git**: For version control +- **OpenAI API Key**: For testing API integration +- **pip**: For package management + +## Setting Up Your Development Environment + +```mermaid +flowchart TD + A[Clone Repository] --> B[Set Up Virtual Environment] + B --> C[Install Dependencies] + C --> D[Configure API Key] + D --> E[Run Tests] + E --> F[Start Development] + + style A fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style C fill:#d3f6db,stroke:#333,stroke-width:2px + style D fill:#d3f6f5,stroke:#333,stroke-width:2px + style E fill:#f5d5f5,stroke:#333,stroke-width:2px + style F fill:#f5f5d5,stroke:#333,stroke-width:2px +``` + +### Step 1: Clone the Repository + +```bash +# If you're a contributor, fork the repository first, then: +git clone https://github.com/YOUR_USERNAME/command-assistant.git + +# If you're a maintainer: +git clone https://github.com/user/command-assistant.git + +# Navigate to the directory +cd command-assistant +``` + +### Step 2: Create a Virtual Environment + +```bash +# Create virtual environment +python -m venv venv + +# Activate virtual environment +# On macOS/Linux: +source venv/bin/activate + +# On Windows: +venv\Scripts\activate +``` + +### Step 3: Install in Development Mode + +```bash +# Install Edison in development mode +pip install -e . + +# Install development dependencies +pip install pylint pytest +``` + +### Step 4: Configure OpenAI API Key + +For development, it's best to use environment variables: + +```bash +# On macOS/Linux: +export OPENAI_API_KEY="your-api-key-here" + +# On Windows: +set OPENAI_API_KEY=your-api-key-here +``` + +Or create a `.env` file in the project root: + +``` +OPENAI_API_KEY="your-api-key-here" +``` + +## Directory Structure Setup + +The development environment should match this structure: + +``` +command-assistant/ # Root directory +├── docs/ # Documentation +├── edison/ # Main package +├── tests/ # Test directory (future addition) +├── venv/ # Virtual environment (generated) +├── .gitignore # Git ignore file +├── LICENSE # License file +├── README.md # Project readme +├── install_edison.sh # Installation script for Linux/macOS +├── install_edison.bat # Installation script for Windows +├── requirements.txt # Package dependencies +└── setup.py # Package setup file +``` + +## Running Edison in Development Mode + +```bash +# Run Edison from the command line +edison your query here + +# Run Edison as a module +python -m edison your query here + +# Run with verbose logging +edison -v your query here +``` + +## Development Tools + +### Code Linting + +Edison uses `pylint` for code linting: + +```bash +# Lint the entire package +pylint edison + +# Lint a specific file +pylint edison/cli.py +``` + +### Testing + +In the future, Edison will use `pytest` for testing: + +```bash +# Run all tests +pytest + +# Run specific tests +pytest tests/test_api_client.py + +# Run with coverage +pytest --cov=edison +``` + +## Debugging + +### Logging + +Enable verbose logging for debugging: + +```bash +edison -v your query here +``` + +Logs are stored in `edison/logs/edison.log`. + +### Using a Debugger + +For detailed debugging, you can use Python's built-in debugger or an IDE: + +```python +import pdb; pdb.set_trace() # Add this line where you want to break + +# Or with Python 3.7+ +breakpoint() +``` + +## Development Workflow + +```mermaid +stateDiagram-v2 + [*] --> Feature + Feature --> Development + Development --> Testing + Testing --> Refinement + Refinement --> Testing: Issues found + Testing --> PullRequest: Tests pass + PullRequest --> Review + Review --> Refinement: Changes requested + Review --> Merge: Approved + Merge --> [*] +``` + +1. **Pick a Feature/Issue**: Select something to work on +2. **Create a Branch**: Make a new branch for your work +3. **Development**: Write code, docstrings, and comments +4. **Testing**: Test your changes thoroughly +5. **Pull Request**: Submit your changes for review +6. **Review**: Address any feedback +7. **Merge**: Changes are merged into main branch + +## IDE Setup + +### VS Code + +Recommended settings for `settings.json`: + +```json +{ + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "python.formatting.blackArgs": ["--line-length", "100"], + "editor.formatOnSave": true, + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "python.testing.nosetestsEnabled": false, + "python.testing.pytestArgs": ["tests"] +} +``` + +### PyCharm + +Recommended settings: +- Enable pylint integration +- Set code style to PEP 8 +- Configure pytest as the test runner + +## Pre-Commit Hooks (Future Addition) + +In the future, we'll add pre-commit hooks for: +- Code formatting with `black` +- Import sorting with `isort` +- Linting with `pylint` +- Type checking with `mypy` + +## Troubleshooting Development Issues + +### Common Issues and Solutions + +| Issue | Solution | +|-------|----------| +| `ModuleNotFoundError` | Ensure your virtual environment is activated and package is installed with `pip install -e .` | +| Import errors | Check your PYTHONPATH and package structure | +| API errors | Verify your API key is set correctly | +| Permission denied | Check file permissions, especially for executable scripts | + +### Getting Help + +If you encounter issues during development: +- Check the documentation +- Search for similar issues on GitHub +- Ask for help in your pull request +- Open a new issue describing your problem \ No newline at end of file diff --git a/docs/user/README.md b/docs/user/README.md new file mode 100644 index 0000000..d8b2cbd --- /dev/null +++ b/docs/user/README.md @@ -0,0 +1,49 @@ +# Edison User Documentation + +Welcome to the Edison user documentation! This guide will help you understand how to use Edison effectively in your day-to-day terminal usage. + +## Table of Contents + +1. [Installation](installation.md) +2. [Basic Usage](basic-usage.md) +3. [Advanced Features](advanced-features.md) +4. [Configuration](configuration.md) +5. [Troubleshooting](troubleshooting.md) + +## Quick Start + +Edison is a command-line tool that translates natural language into shell commands. To use Edison, simply type `edison` followed by your query: + +```bash +edison how to compress all jpg files in the current directory +``` + +Edison will generate the appropriate command and ask for your confirmation before executing it. + +## Feature Overview + +```mermaid +flowchart TD + A[Edison CLI] --> B[Natural Language Processing] + B --> C{Command Generation} + C --> D[Command Execution] + C --> E[Command Explanation] + C --> F[Interactive Mode] + + style A fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style C fill:#d3f6db,stroke:#333,stroke-width:2px + style D fill:#d3f6f5,stroke:#333,stroke-width:2px + style E fill:#d3f6f5,stroke:#333,stroke-width:2px + style F fill:#d3f6f5,stroke:#333,stroke-width:2px +``` + +Edison offers several key features: + +- **Natural Language Command Generation**: Describe what you want to do in plain English +- **Command Explanation**: Get explanations for complex commands +- **Interactive Mode**: Use Edison in an interactive shell +- **Shell Integration**: Works with your default shell +- **Configurable**: Adjust settings via `edison.yaml` + +Check the individual documentation pages for more detailed information on each feature. \ No newline at end of file diff --git a/docs/user/advanced-features.md b/docs/user/advanced-features.md new file mode 100644 index 0000000..ac97556 --- /dev/null +++ b/docs/user/advanced-features.md @@ -0,0 +1,190 @@ +# Advanced Features + +Edison offers several advanced features to enhance your productivity. This guide explores these capabilities in detail. + +## Interactive Mode + +Interactive mode provides a dedicated shell for continuous Edison interactions. + +```mermaid +stateDiagram-v2 + [*] --> Interactive: edison -i + Interactive --> Query: Enter query + Query --> CommandGeneration: Process query + CommandGeneration --> CommandDisplay: Display command + CommandDisplay --> ActionChoice: Prompt for action + ActionChoice --> Execution: y/Enter + ActionChoice --> Modification: m + ActionChoice --> Explanation: x + ActionChoice --> Clipboard: c + ActionChoice --> Query: n/skip + Execution --> Query: Command executed + Modification --> Execution: Execute modified command + Explanation --> ActionChoice: Show explanation + Clipboard --> ActionChoice: Copy to clipboard + Interactive --> [*]: exit/quit/!exit/Ctrl+D +``` + +To start interactive mode: + +```bash +edison -i +``` + +In interactive mode, you can: +- Enter queries one after another without restarting Edison +- Access command history with up/down arrows +- Use tab completion for Edison commands +- Type `!help` to see available commands +- Type `!exit`, `exit`, `!quit`, `quit` or press `Ctrl+D` to exit + +## Command Explanations + +Get explanations for complex commands: + +```bash +edison -e how to find duplicate files in a directory +``` + +The `-e` (or `--explain`) flag makes Edison provide an explanation of the generated command, breaking it down part by part. Edison uses terminal-friendly formatting to make explanations more readable: + +- **Bold text** for emphasis and headers +- **Green text** for commands and file paths +- **Yellow text** for command parameters and options +- **Cyan text** for section beginnings and important concepts +- **Code formatting** for command snippets and examples + +In interactive mode, use the `x` option when prompted for an action to get an explanation. + +## Configuration + +Edison can be configured through the `edison.yaml` file. This configuration file is located in the Edison installation directory. + +Here's an example configuration: + +```yaml +# OpenAI API settings +model: gpt-3.5-turbo +temperature: 0 +max_tokens: 500 + +# Safety settings +safety: true + +# Shell settings +shell: bash +``` + +Configuration options: + +| Option | Description | Default | +|--------|-------------|---------| +| model | OpenAI model to use | gpt-3.5-turbo | +| temperature | Randomness of completions (0-1) | 0 | +| max_tokens | Maximum response tokens | 500 | +| safety | Require confirmation before execution | true | +| shell | Shell to use for command execution | system default | +| openai_api_key | OpenAI API key (optional) | - | + +You can view your current configuration with: + +```bash +edison -c +``` + +## Logging and Debugging + +Edison provides verbose logging for troubleshooting: + +```bash +edison -v list all processes +``` + +With the `-v` (or `--verbose`) flag, Edison will output detailed DEBUG level logs. + +Log files are stored in the `edison/logs` directory and can be useful for debugging issues. + +## Streaming Command Generation + +Edison supports streaming command generation, which shows the command being generated in real-time: + +```mermaid +sequenceDiagram + participant User + participant Edison + participant API as OpenAI API + + User->>Edison: Natural language query + Edison->>API: Request with streaming enabled + Note over Edison,API: Streaming connection established + API-->>Edison: Token: "f" + Edison->>User: Display: "f" + API-->>Edison: Token: "ind" + Edison->>User: Update: "find" + API-->>Edison: Token: " -name" + Edison->>User: Update: "find -name" + API-->>Edison: Token: " \"*.txt\"" + Edison->>User: Update: "find -name \"*.txt\"" + API-->>Edison: Token: " -type f" + Edison->>User: Final: "find -name \"*.txt\" -type f" +``` + +This feature: +- Provides immediate feedback during command generation +- Makes Edison feel more responsive, especially for complex commands +- Allows you to see the thought process in real-time + +### Hiding the "Generating command:" Prefix + +By default, Edison displays "Generating command:" while it's working. You can hide this prefix with the following configuration: + +```yaml +# In edison.yaml +show_generating_prefix: false +``` + +## Direct Command Modification + +Edison provides a direct command modification option that allows you to edit the generated command in-place: + +1. When prompted for action, press `d` to directly modify the command +2. Edison will present an interactive editor with the current command +3. Edit the command as needed +4. Press Enter to execute the modified command + +This feature is especially useful for: +- Making minor adjustments to generated commands +- Adding flags or options that weren't included +- Correcting parts of the command that may not be exactly what you want + +## Performance Optimization + +For faster response times: + +1. Use a faster model like `gpt-3.5-turbo` (default) instead of larger models +2. Keep queries concise and specific +3. Use interactive mode to avoid startup overhead for multiple commands +4. Enable streaming mode for perceived performance improvements + +## Safety Features + +Edison includes built-in safety measures: + +1. **Command confirmation**: By default, Edison asks for confirmation before executing commands +2. **Dangerous command detection**: Edison will warn about potentially dangerous operations +3. **Markdown filtering**: Commands containing markdown formatting are not executed directly + +To disable the safety confirmation (not recommended): + +```yaml +# In edison.yaml +safety: false +``` + +## Cross-Platform Considerations + +Edison works on Linux, macOS, and Windows, but some commands might be platform-specific. For best results: + +1. Mention your operating system in queries for system-specific commands +2. Use the `-e` flag to understand what a command does before executing it +3. Be aware that file paths use different separators (/ vs \\) on different platforms \ No newline at end of file diff --git a/docs/user/basic-usage.md b/docs/user/basic-usage.md new file mode 100644 index 0000000..82d7c06 --- /dev/null +++ b/docs/user/basic-usage.md @@ -0,0 +1,94 @@ +# Basic Usage + +Edison is designed to be intuitive and easy to use. This guide covers the fundamental operations and commands. + +## Command Structure + +``` +edison [OPTIONS] +``` + +For example: + +```bash +edison how to find all png files in the current directory +``` + +## Basic Workflow + +```mermaid +sequenceDiagram + participant User + participant Edison + participant API as OpenAI API + participant Shell + + User->>Edison: Natural language query + Edison->>API: Send prompt with query + API-->>Edison: Return command + Edison->>User: Display command & ask for confirmation + User->>Edison: Confirm (Y/n/m) + alt User confirms + Edison->>Shell: Execute command + Shell-->>User: Show command output + else User modifies + User->>Edison: Modified command + Edison->>Shell: Execute modified command + Shell-->>User: Show command output + else User declines + Edison->>User: Command skipped + end +``` + +1. **Enter your query**: Describe in natural language what you want to do +2. **Review the command**: Edison will display the generated shell command +3. **Choose an action**: + - Press `Enter` or type `y` to execute the command + - Type `n` to skip execution + - Type `m` to modify the command before execution + - Type `d` to directly modify the command (new feature!) + - Type `c` to copy the command to clipboard + +## Command Line Options + +Edison supports several command-line options: + +| Option | Description | +|--------|-------------| +| `-v, --verbose` | Enable verbose logging (DEBUG level) | +| `-i, --interactive` | Start interactive shell mode | +| `-e, --explain` | Explain the generated command | +| `-c, --config` | Print current configuration | +| `-s, --safety` | Enable safety mode (only useful when safety is off) | + +## Examples + +Here are some example queries you can try: + +```bash +# File operations +edison list all files larger than 10MB +edison create a new directory called "project" +edison find files modified in the last 24 hours + +# System information +edison check CPU usage +edison show network connections +edison how much disk space do I have left + +# Text processing +edison count lines in all python files +edison find lines containing "error" in log files +edison replace "old" with "new" in all text files +``` + +## Tips for Effective Queries + +1. **Be specific**: The more specific your query, the better the generated command +2. **Include parameters**: Mention specific filenames, sizes, or other parameters +3. **Start with verbs**: Commands usually work best when starting with actions like "find", "list", "create" +4. **Use common terminology**: Avoid obscure technical terms when possible + +## Next Steps + +Once you're comfortable with basic usage, explore [Advanced Features](advanced-features.md) to learn about interactive mode, command explanations, and more. \ No newline at end of file diff --git a/docs/user/configuration.md b/docs/user/configuration.md new file mode 100644 index 0000000..ecc090a --- /dev/null +++ b/docs/user/configuration.md @@ -0,0 +1,152 @@ +# Configuration + +Edison provides several ways to configure its behavior to suit your needs. This guide explains the available configuration options and how to set them. + +## Configuration File + +The primary way to configure Edison is through the `edison.yaml` file. This file is located in the Edison installation directory. + +```mermaid +flowchart LR + A[edison.yaml] --> B[OpenAI Settings] + A --> C[Safety Settings] + A --> D[Shell Settings] + + B --> B1[Model] + B --> B2[Temperature] + B --> B3[Max Tokens] + B --> B4[API Key] + + C --> C1[Safety Mode] + + D --> D1[Shell Type] + + style A fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style C fill:#d3f6db,stroke:#333,stroke-width:2px + style D fill:#d3f6f5,stroke:#333,stroke-width:2px +``` + +### Sample Configuration File + +Here's an example `edison.yaml` file with all available options: + +```yaml +# OpenAI API settings +model: gpt-4o-mini +temperature: 0 +max_tokens: 500 +openai_api_key: your_api_key_here # Optional, can be set via environment variable +streaming: true # Enable streaming command generation + +# Safety settings +safety: true + +# UI settings +show_generating_prefix: true # Show "Generating command:" prefix during generation + +# Shell settings +shell: bash # Options: bash, zsh, powershell.exe, cmd.exe, etc. +``` + +## Configuration Options + +### OpenAI API Settings + +| Option | Description | Default | Possible Values | +|--------|-------------|---------|----------------| +| model | OpenAI model to use | gpt-4o-mini | gpt-4o-mini, gpt-3.5-turbo, gpt-4, etc. | +| temperature | Randomness of completions | 0 | 0.0 - 1.0 | +| max_tokens | Maximum tokens in response | 500 | 1 - 4096 | +| openai_api_key | Your OpenAI API key | - | Valid API key | +| streaming | Enable streaming command generation | true | true, false | + +### Safety Settings + +| Option | Description | Default | Possible Values | +|--------|-------------|---------|----------------| +| safety | Require confirmation before execution | true | true, false | + +### UI Settings + +| Option | Description | Default | Possible Values | +|--------|-------------|---------|----------------| +| show_generating_prefix | Show "Generating command:" prefix during generation | true | true, false | + +### Shell Settings + +| Option | Description | Default | Possible Values | +|--------|-------------|---------|----------------| +| shell | Shell to use for command execution | (system default) | bash, zsh, powershell.exe, cmd.exe, etc. | + +## Setting Your OpenAI API Key + +There are several ways to set your OpenAI API key, listed in order of precedence: + +1. **Environment variable**: + - Linux/macOS: `export OPENAI_API_KEY=` + - Windows: `$env:OPENAI_API_KEY=""` + +2. **API key file**: + Create a file at `~/.openai.apikey` containing only your API key. + +3. **Configuration file**: + Add your key to the `edison.yaml` file: + ```yaml + openai_api_key: your_api_key_here + ``` + +## Viewing Current Configuration + +To view your current configuration: + +```bash +edison -c +``` + +This will display all current settings, including where your OpenAI API key is being loaded from (but will not display the key itself for security reasons). + +## Configuration Load Order + +Edison loads configuration in this order: + +1. Default values +2. Values from `edison.yaml` +3. Environment variables (for API key) +4. Command-line arguments + +Later sources override earlier ones. + +## Recommended Configurations + +### For Beginners + +```yaml +model: gpt-4o-mini +temperature: 0 +max_tokens: 500 +safety: true +``` + +### For Advanced Users + +```yaml +model: gpt-4 # If you have access +temperature: 0.2 # Slightly more creative +max_tokens: 1000 # For more detailed responses +safety: true # Keep safety on unless you really know what you're doing +``` + +### For Performance + +```yaml +model: gpt-3.5-turbo # Fastest option if you need maximum speed +temperature: 0 +max_tokens: 300 # Lower limits can be faster +``` + +## Troubleshooting Configuration Issues + +- **Configuration not applying**: Ensure your `edison.yaml` file is in the correct location +- **API key not recognized**: Check for extra whitespace or quotes in your key +- **Model not available**: Verify you have access to the specified model in your OpenAI account \ No newline at end of file diff --git a/docs/user/installation.md b/docs/user/installation.md new file mode 100644 index 0000000..1e7cb2d --- /dev/null +++ b/docs/user/installation.md @@ -0,0 +1,109 @@ +# Edison Installation Guide + +This guide covers how to install Edison on different operating systems. + +## Prerequisites + +Before installing Edison, ensure you have: + +- Python 3.6 or higher +- pip (Python package manager) +- An OpenAI API key + +## Installation Process + +```mermaid +flowchart LR + A[Get Repository] --> B[Install Dependencies] + B --> C[Configure API Key] + C --> D[Test Installation] + + style A fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style C fill:#d3f6db,stroke:#333,stroke-width:2px + style D fill:#d3f6f5,stroke:#333,stroke-width:2px +``` + +### Linux and macOS + +1. Clone the repository: + ```bash + git clone https://github.com/user/command-assistant + cd command-assistant + ``` + +2. Run the installation script: + ```bash + source install_edison.sh + ``` + + This script will: + - Create a virtual environment + - Install required dependencies + - Set up the Edison command + +3. Configure your OpenAI API key using one of these methods: + - Environment variable: `export OPENAI_API_KEY=` + - Create a file at `~/.openai.apikey` with just the key + - Add the key to the `edison.yaml` configuration file + +### Windows + +1. Clone the repository: + ```powershell + git clone https://github.com/user/command-assistant + cd command-assistant + ``` + +2. Run the installation script: + ```powershell + .\install_edison.bat + ``` + +3. Configure your OpenAI API key using one of these methods: + - Environment variable: `$env:OPENAI_API_KEY=""` + - Create a file at `~/.openai.apikey` with just the key + - Add the key to the `edison.yaml` configuration file + +## Manual Installation + +If you prefer to install manually: + +1. Clone the repository: + ```bash + git clone https://github.com/user/command-assistant + cd command-assistant + ``` + +2. Create and activate a virtual environment: + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. Install the package: + ```bash + pip install -e . + ``` + +4. Configure your OpenAI API key as described above + +## Verifying Installation + +To test your installation, run: + +```bash +edison what is the current time +``` + +You should see a command generated that displays the current time. + +## Troubleshooting + +If you encounter issues during installation: + +- Ensure Python 3.6+ is installed and in your PATH +- Check that your OpenAI API key is correctly set +- Verify that all dependencies were installed correctly + +See the [Troubleshooting](troubleshooting.md) page for more detailed help. \ No newline at end of file diff --git a/docs/user/themes.md b/docs/user/themes.md new file mode 100644 index 0000000..91247ec --- /dev/null +++ b/docs/user/themes.md @@ -0,0 +1,150 @@ +# Edison Themes Guide + +Edison supports rich syntax highlighting for commands through its theme system. This guide explains how to use and customize themes in Edison. + +## Available Themes + +Edison uses the Rich library's syntax highlighting capabilities, which are powered by Pygments. The following themes are available out of the box: + +| Theme Name | Description | Style | +|------------|-------------|-------| +| monokai | Default theme with vibrant colors | Dark | +| github-dark | Based on GitHub's dark theme | Dark | +| solarized-dark | Popular dark theme with softer colors | Dark | +| dracula | High contrast dark theme | Dark | +| nord | Bluish dark theme | Dark | +| gruvbox-dark | Retro dark theme with warm colors | Dark | +| one-dark | Based on Atom's One Dark theme | Dark | +| vs | Based on Visual Studio's default theme | Light | +| solarized-light | Light version of the Solarized theme | Light | +| gruvbox-light | Light version of the Gruvbox theme | Light | + +## Using Themes + +You can specify a theme in three ways: + +### 1. In the Configuration File + +Edit your `edison.yaml` file to set the default theme: + +```yaml +ui: + theme: "github-dark" # Change from the default "monokai" +``` + +### 2. Via Command-Line Argument + +Specify a theme for a single command: + +```bash +edison --theme dracula "list all files" +``` + +### 3. In Interactive Mode + +The theme specified in the configuration or command-line will be used in interactive mode as well. + +## Theme Elements + +Themes apply different colors to various syntax elements: + +- **Keywords**: Shell keywords like `if`, `for`, `while`, etc. +- **Strings**: Text enclosed in quotes +- **Comments**: Lines starting with `#` in bash +- **Variables**: Names prefixed with `$` in bash +- **Operators**: Symbols like `=`, `-gt`, `|`, etc. +- **Functions**: Function names and calls +- **Numbers**: Numeric literals +- **Punctuation**: Brackets, parentheses, etc. + +## Creating Custom Themes + +While Edison doesn't directly support custom themes, you can create your own by extending the Rich library: + +1. Create a custom theme file (e.g., `custom_themes.py`): + +```python +from pygments.style import Style +from pygments.token import ( + Comment, Error, Keyword, Literal, Name, Number, Operator, String, Text +) + +class CustomTheme(Style): + """A custom syntax highlighting theme.""" + + # Define colors + background_color = "#282c34" + highlight_color = "#3e4451" + + # Define token colors + styles = { + Text: "#abb2bf", + Error: "#e06c75", + Comment: "#5c6370", + Keyword: "#c678dd", + Keyword.Reserved: "#c678dd", + Keyword.Namespace: "#c678dd", + Name: "#abb2bf", + Name.Builtin: "#e5c07b", + Name.Function: "#61afef", + Name.Class: "#e5c07b", + Name.Decorator: "#61afef", + Name.Variable: "#e06c75", + Number: "#d19a66", + Operator: "#56b6c2", + String: "#98c379", + } +``` + +2. Register your theme with Pygments: + +```python +from pygments.styles import STYLE_MAP +STYLE_MAP["custom"] = "custom_themes.CustomTheme" +``` + +3. Use your custom theme: + +```python +from edison.utils.markdown_utils import print_command_rich +print_command_rich("ls -la", theme="custom") +``` + +## Theme Compatibility + +Theme appearance may vary depending on: + +1. **Terminal Capabilities**: Some terminals have limited color support or override colors with their own theme settings. + +2. **Color Schemes**: Your terminal's color scheme may affect how themes appear. + +3. **Font Settings**: Certain font features like ligatures can affect the display of syntax-highlighted code. + +## Troubleshooting + +If you're not seeing theme differences: + +1. **Check Terminal Support**: Ensure your terminal supports 256 colors or true color: + ```bash + echo $TERM + echo $COLORTERM + ``` + + For best results, use a terminal that supports true color (24-bit color). + +2. **Try High-Contrast Themes**: If differences are subtle, try themes with higher contrast like `dracula` vs `solarized-light`. + +3. **Use Complex Commands**: Simple commands might not have enough syntax elements to show significant differences between themes. + +## Demo Scripts + +Edison includes two demonstration scripts to help you visualize theme differences: + +1. `theme_demo.py`: Shows the same bash script rendered with different themes +2. `edison_theme_demo.py`: Demonstrates how Edison's theme functionality works with its markdown_utils module + +Run these scripts to see the themes in action: + +```bash +python theme_demo.py +python edison_theme_demo.py diff --git a/docs/user/troubleshooting.md b/docs/user/troubleshooting.md new file mode 100644 index 0000000..dcbbc99 --- /dev/null +++ b/docs/user/troubleshooting.md @@ -0,0 +1,128 @@ +# Troubleshooting + +This guide helps you diagnose and resolve common issues with Edison. + +## Common Issues and Solutions + +```mermaid +flowchart TB + Start[Issue Detected] --> A{API Related?} + A -->|Yes| B[API Issues] + A -->|No| C{Installation Related?} + C -->|Yes| D[Installation Issues] + C -->|No| E{Command Related?} + E -->|Yes| F[Command Issues] + E -->|No| G[Other Issues] + + B --> B1[Check API Key] + B --> B2[Check Network] + B --> B3[Check Model Access] + + D --> D1[Check Python Version] + D --> D2[Check Dependencies] + D --> D3[Check PATH] + + F --> F1[Check Command Syntax] + F --> F2[Check Shell Compatibility] + + style Start fill:#f9d5e5,stroke:#333,stroke-width:2px + style B fill:#eeeeee,stroke:#333,stroke-width:2px + style D fill:#d3f6db,stroke:#333,stroke-width:2px + style F fill:#d3f6f5,stroke:#333,stroke-width:2px + style G fill:#f5d3d3,stroke:#333,stroke-width:2px +``` + +### API Issues + +| Issue | Solution | +|-------|----------| +| `No OpenAI API key found` | Set your API key using one of the [configuration methods](configuration.md#setting-your-openai-api-key) | +| `Rate limit exceeded` | Wait a minute before trying again, or check your OpenAI rate limits | +| `Invalid API key` | Verify your API key is correct and has not expired | +| `Model not available` | Ensure you have access to the requested model, or change to `gpt-3.5-turbo` | + +### Installation Issues + +| Issue | Solution | +|-------|----------| +| `Command not found: edison` | Ensure Edison is installed and in your PATH | +| `No module named 'openai'` | Reinstall Edison or run `pip install -r requirements.txt` | +| `ModuleNotFoundError` | Ensure all dependencies are installed correctly | +| `Python version error` | Ensure you're using Python 3.6+ | + +### Command Execution Issues + +| Issue | Solution | +|-------|----------| +| `Command contains markdown` | Edison detected markdown in the response; rerun the query | +| `Command execution failed` | The generated command has syntax errors; try modifying it | +| `Permission denied` | The command requires higher permissions; prefix with sudo if appropriate | +| `Command not found` | The generated command uses a program not installed on your system | + +### Other Issues + +| Issue | Solution | +|-------|----------| +| `Slow response times` | Try a faster model or check your internet connection | +| `Inaccurate commands` | Be more specific in your query or add more context | +| `Unexpected behavior` | Check the logs for detailed error information | + +## Enabling Verbose Mode + +For more detailed troubleshooting, use verbose mode: + +```bash +edison -v your query here +``` + +This will print detailed logs that can help identify issues. + +## Checking Logs + +Log files are stored in the `edison/logs` directory. The main log file is `edison.log`. Check this file for detailed error messages and API interactions. + +```bash +cat edison/logs/edison.log | tail -n 50 +``` + +## Resetting Configuration + +If you suspect a configuration issue, you can reset to defaults by removing or renaming your `edison.yaml` file: + +```bash +mv edison.yaml edison.yaml.bak +``` + +Edison will create a new configuration file with default settings on the next run. + +## Common Error Messages and Meanings + +### "No OpenAI API key found" + +You need to set your OpenAI API key. See [configuration options](configuration.md). + +### "Error calling OpenAI API" + +There was an issue communicating with the OpenAI API. Check: +1. Your internet connection +2. API key validity +3. OpenAI service status + +### "Response does not contain a valid command" + +The AI generated a response that Edison couldn't interpret as a command. Try: +1. Rephrasing your query to be more specific +2. Checking logs for the full response +3. Using a different model + +## Getting Help + +If you continue to experience issues: + +1. Check the full documentation +2. Look for similar issues in the GitHub repository +3. Open a new issue with: + - A clear description of the problem + - Steps to reproduce + - Any error messages (with sensitive information redacted) + - Your OS and Python version \ No newline at end of file diff --git a/edison/README.md b/edison/README.md new file mode 100644 index 0000000..ee7ca08 --- /dev/null +++ b/edison/README.md @@ -0,0 +1,100 @@ +# Edison + +Edison is an AI-powered command-line tool that translates natural language queries into shell commands. It leverages OpenAI's GPT models to generate accurate and useful commands based on your descriptions. + +## Features + +- Translate natural language to shell commands +- Support for multiple shells (bash, zsh, powershell) +- Safety checks for potentially dangerous commands +- Command modification and clipboard support +- Configurable via YAML file +- Rich terminal UI with syntax highlighting +- Interactive shell mode with command history +- Command explanations +- Progress indicators and spinners + +## Installation + +```bash +# Clone the repository +git clone https://github.com/yourusername/edison.git +cd edison + +# Install the package +pip install -e . +``` + +## Usage + +```bash +# Basic usage +edison how to list all files in the current directory + +# Enable safety mode (confirmation before execution) +edison -s how to find all python files recursively + +# Print current configuration +edison -c + +# Enable verbose logging +edison -v how to check disk space + +# Use rich terminal UI +edison -r how to find large files + +# Get command explanations +edison -e how to compress a directory + +# Start interactive shell mode +edison -i +``` + +## Interactive Shell + +Edison includes an interactive shell mode that provides a more user-friendly experience: + +```bash +# Start interactive shell +edison -i +``` + +In interactive mode, you can: +- Type natural language queries +- Navigate command history with up/down arrows +- Get auto-suggestions based on previous commands +- Execute, modify, copy, or get explanations for commands +- Use special commands like `!help` and `!exit` + +## Rich Terminal UI + +The rich terminal UI provides: +- Syntax highlighting for commands +- Colorful output +- Progress indicators during API calls +- Command explanations with detailed breakdowns +- Success/error messages with appropriate styling + +## Configuration + +Edison can be configured via the `edison.yaml` file. Here's an example configuration: + +```yaml +model: gpt-3.5-turbo +temperature: 0 +max_tokens: 500 +safety: true +``` + +## API Key + +Edison requires an OpenAI API key to function. You can provide it in one of the following ways: + +1. Environment variable: `OPENAI_API_KEY="your-api-key"` +2. `.env` file in the same directory as the script +3. `.openai.apikey` file in your home directory +4. In the `edison.yaml` configuration file: `openai_api_key: "your-api-key"` + +## License + +MIT diff --git a/edison/__init__.py b/edison/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edison/__main__.py b/edison/__main__.py new file mode 100644 index 0000000..c9ece69 --- /dev/null +++ b/edison/__main__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +""" +Entry point for running the edison package as a module. +""" +from edison.cli import main + +if __name__ == "__main__": + main() diff --git a/edison/cli.py b/edison/cli.py new file mode 100644 index 0000000..b0f218c --- /dev/null +++ b/edison/cli.py @@ -0,0 +1,128 @@ +""" +Command-line interface for the Edison application. +""" +import argparse +import logging +from colorama import init +from edison.config import config_manager +from edison.core import api_client +from edison.ui import console, interactive +from edison.utils import logging_utils, os_utils + +# Logger will be initialized after parsing args +logger = None + +def parse_arguments(): + """ + Parse command line arguments. + + Returns: + argparse.Namespace: The parsed arguments. + """ + parser = argparse.ArgumentParser( + description='AI bot that translates your question to a command.' + ) + parser.add_argument('text', nargs='*', + help='A sequence of strings') + parser.add_argument("-s", "--safety", action='store_true', + help='Enable safety mode (only useful when safety is off)') + parser.add_argument("-c", "--config", action='store_true', + help='Print current configuration') + parser.add_argument("-v", "--verbose", action='store_true', + help='Enable verbose logging (DEBUG level)') + parser.add_argument("-i", "--interactive", action='store_true', + help='Start interactive shell mode') + parser.add_argument("-e", "--explain", action='store_true', + help='Explain the generated command') + parser.add_argument("--no-streaming", action='store_true', + help='Disable streaming output for command generation') + parser.add_argument("--no-rich", action='store_true', + help='Disable rich text formatting') + parser.add_argument("--theme", type=str, + help='Set syntax highlighting theme (e.g., monokai, github-dark)') + return parser.parse_args() + +def main(): + """ + Main entry point for the application. + """ + # Parse command line arguments + args = parse_arguments() + + # Initialize logging based on the verbose flag + global logger + logger = logging_utils.setup_logging(verbose=args.verbose) + + try: + # Load configuration + config = config_manager.load_config() + logger.debug("Configuration loaded") + + # Process command-line arguments + if args.safety: + config["safety"] = args.safety + + # Set streaming and UI options based on command-line arguments + if args.no_streaming: + config["streaming"] = False + + # Initialize UI config if not present + if "ui" not in config: + config["ui"] = {} + + # Set rich formatting option + if args.no_rich: + config["ui"]["rich_formatting"] = False + + # Set theme if specified + if args.theme: + config["ui"]["theme"] = args.theme + + # Set default shell if not specified + if "shell" not in config: + config["shell"] = os_utils.get_default_shell() + + # Print configuration if requested + if args.config: + config_manager.print_config(config) + return + + # Enable color output on Windows using colorama + init() + + # Initialize API client + try: + client = api_client.create_client(config) + logger.debug("API client initialized") + + # Start interactive mode if requested + if args.interactive: + interactive.interactive_mode(client, config) + return + + # Check if we have a query + if not args.text: + print("No query provided. Use -i for interactive mode.") + return + + # Join the text arguments + user_prompt = " ".join(args.text) + + # Generate command with or without streaming based on config + if config.get("streaming", True): + # Use streaming version + console.handle_command_execution_streaming(client, config, user_prompt, explain=args.explain) + else: + # Use non-streaming version + command = api_client.generate_command(client, config, user_prompt) + logger.debug("Command generated") + console.handle_command_execution(client, config, command, explain=args.explain) + except Exception as e: + logger.error(f"Error: {e}") + print(f"Error: {e}") + except Exception as e: + logger.error(f"Unhandled exception: {e}") + print(f"An unexpected error occurred: {e}") + +if __name__ == "__main__": + main() diff --git a/edison/config/__init__.py b/edison/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edison/config/config_manager.py b/edison/config/config_manager.py new file mode 100644 index 0000000..558eeaf --- /dev/null +++ b/edison/config/config_manager.py @@ -0,0 +1,60 @@ +""" +Configuration management for the Edison application. +""" +import os +import yaml +import logging + +logger = logging.getLogger(__name__) + +def get_config_path(): + """ + Get the path to the configuration file. + + Returns: + str: The path to the configuration file. + """ + script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + return os.path.join(script_dir, "edison.yaml") + +def load_config(): + """ + Load configuration from file. + + Returns: + dict: The loaded configuration. + """ + config_path = get_config_path() + try: + with open(config_path, 'r') as file: + config = yaml.safe_load(file) + logger.debug(f"Configuration loaded from {config_path}") + return config + except Exception as e: + logger.error(f"Error loading configuration: {e}") + return {} + +def print_config(config): + """ + Print configuration information. + + Args: + config (dict): The configuration dictionary. + """ + print("Current configuration per edison.yaml:") + print("— Model : " + str(config.get("model", "Not specified"))) + print("— Temperature : " + str(config.get("temperature", "Not specified"))) + print("— Max. Tokens : " + str(config.get("max_tokens", "Not specified"))) + print("— Safety : " + str(bool(config.get("safety", False)))) + print("— Streaming : " + str(bool(config.get("streaming", True)))) + print("— Show Prefix : " + str(bool(config.get("show_generating_prefix", True)))) + print("— Shell : " + str(config.get("shell", "Not specified"))) + + # Print UI configuration if available + ui_config = config.get("ui", {}) + if ui_config: + print("\nUI Configuration:") + print("— Rich Formatting : " + str(bool(ui_config.get("rich_formatting", True)))) + print("— Theme : " + str(ui_config.get("theme", "monokai"))) + print("— Command Style : " + str(ui_config.get("command_style", "panel"))) + print("— Structured Expl.: " + str(bool(ui_config.get("structured_explanations", True)))) diff --git a/edison/core/__init__.py b/edison/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edison/core/api_client.py b/edison/core/api_client.py new file mode 100644 index 0000000..53349ad --- /dev/null +++ b/edison/core/api_client.py @@ -0,0 +1,218 @@ +""" +OpenAI API client for the Edison application. +""" +import os +import time +import logging +import dotenv +from openai import OpenAI +from edison.core import prompt_manager +from edison.utils import logging_utils + +logger = logging.getLogger(__name__) + +def get_api_key(config): + """ + Get the OpenAI API key from various sources. + + This function first tries to grab the OpenAI API key from environment variables, + if not found, it then looks for the key in the `.openai.apikey` in the home directory, + and lastly, it will look in the provided config dictionary. + + Args: + config (dict): A dictionary containing configuration values. + It may contain `openai_api_key` as one of the keys. + + Returns: + str: The OpenAI API key + """ + dotenv.load_dotenv() + + # Method 1: Read API key from environment variable + # The user can set their OpenAI API key by creating a ".env" file in the same + # directory as this script or by exporting it to their environment variables. + # The file or environment variable should contain the line `OPENAI_API_KEY=""`. + api_key = os.getenv("OPENAI_API_KEY") + + # Method 2: Read API key from a file in the home directory + # The user can also place a file named ".openai.apikey" in their home directory, + # which includes the API key in raw format. This method might be deprecated in future versions. + if not api_key: + home_path = os.path.expanduser("~") + api_key_path = os.path.join(home_path, ".openai.apikey") + if os.path.exists(api_key_path): + with open(api_key_path, 'r') as f: + api_key = f.read().strip() + + # Method 3: Read API key from the provided config dictionary + # The final method to set the API key is by providing it in the 'config' dictionary under the + # key 'openai_api_key'. For instance, in a `edison.yaml` config file, it would appear as + # `openai_apikey: `. + if not api_key: + api_key = config.get("openai_api_key") + + if not api_key: + logger.error("No OpenAI API key found. Please set it in your environment, .env file, or config.") + raise ValueError("No OpenAI API key found") + + return api_key + +def create_client(config): + """ + Create and initialize an OpenAI client. + + Args: + config (dict): The configuration dictionary. + + Returns: + OpenAI: An initialized OpenAI client. + """ + api_key = get_api_key(config) + return OpenAI(api_key=api_key) + +def call_api(client, config, query): + """ + Call the OpenAI API with the given query. + + Args: + client (OpenAI): The OpenAI client instance. + config (dict): Configuration dictionary containing model and parameters. + query (str): The user's query string. + + Returns: + str: The generated command as a string. + """ + if not query: + logger.error("No user prompt specified.") + raise ValueError("No user prompt specified") + + # Load the correct prompt based on shell and OS and append the user's prompt + prompt = prompt_manager.get_full_prompt(query, config.get("shell", "bash")) + + # Extract the system prompt from the first line + system_prompt = prompt.split('\n')[0] if '\n' in prompt else prompt + + try: + # Use the modern API pattern + response = client.chat.completions.create( + model=config.get("model", "gpt-4o-mini"), + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt} + ], + temperature=config.get("temperature", 0), + max_tokens=config.get("max_tokens", 500), + ) + + # Extract the content from the new response structure + return response.choices[0].message.content.strip() + + except Exception as e: + logger.error(f"Error calling OpenAI API: {str(e)}") + raise + +def call_api_streaming(client, config, query, callback): + """ + Call the OpenAI API with streaming enabled. + + Args: + client (OpenAI): The OpenAI client instance. + config (dict): Configuration dictionary containing model and parameters. + query (str): The user's query string. + callback (callable): Function to call with each token as it arrives. + + Returns: + str: The complete generated command as a string. + """ + if not query: + logger.error("No user prompt specified.") + raise ValueError("No user prompt specified") + + # Load the correct prompt based on shell and OS and append the user's prompt + prompt = prompt_manager.get_full_prompt(query, config.get("shell", "bash")) + + # Extract the system prompt from the first line + system_prompt = prompt.split('\n')[0] if '\n' in prompt else prompt + + try: + # Use the streaming API pattern + response = client.chat.completions.create( + model=config.get("model", "gpt-4o-mini"), + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt} + ], + temperature=config.get("temperature", 0), + max_tokens=config.get("max_tokens", 500), + stream=True # Enable streaming + ) + + # Initialize an empty string to collect the full response + full_response = "" + + # Process the streaming response + for chunk in response: + if hasattr(chunk.choices[0].delta, 'content'): + content = chunk.choices[0].delta.content + if content: + # Call the callback with the new content + callback(content) + # Append to the full response + full_response += content + + return full_response.strip() + + except Exception as e: + logger.error(f"Error calling OpenAI API: {str(e)}") + raise + +def generate_command_streaming(client, config, query, callback, max_retries=3): + """ + Generate a command using the OpenAI API with streaming and retry logic. + + Args: + client (OpenAI): The OpenAI client instance. + config (dict): Configuration dictionary containing model and parameters. + query (str): The user's query string. + callback (callable): Function to call with each token as it arrives. + max_retries (int): Maximum number of retry attempts. + + Returns: + str: The generated command as a string. + """ + for attempt in range(max_retries): + try: + return call_api_streaming(client, config, query, callback) + except Exception as e: + if "rate limit" in str(e).lower() and attempt < max_retries - 1: + wait_time = 2 ** attempt # Exponential backoff + logger.warning(f"Rate limited. Retrying in {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error(f"Error after {attempt+1} attempts: {str(e)}") + raise + +def generate_command(client, config, query, max_retries=3): + """ + Generate a command using the OpenAI API with retry logic. + + Args: + client (OpenAI): The OpenAI client instance. + config (dict): Configuration dictionary containing model and parameters. + query (str): The user's query string. + max_retries (int): Maximum number of retry attempts. + + Returns: + str: The generated command as a string. + """ + for attempt in range(max_retries): + try: + return call_api(client, config, query) + except Exception as e: + if "rate limit" in str(e).lower() and attempt < max_retries - 1: + wait_time = 2 ** attempt # Exponential backoff + logger.warning(f"Rate limited. Retrying in {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error(f"Error after {attempt+1} attempts: {str(e)}") + raise diff --git a/edison/core/command_executor.py b/edison/core/command_executor.py new file mode 100644 index 0000000..242365a --- /dev/null +++ b/edison/core/command_executor.py @@ -0,0 +1,39 @@ +""" +Command execution for the Edison application. +""" +import subprocess +import logging +from edison.utils import validation + +logger = logging.getLogger(__name__) + +def execute_command(shell, command): + """ + Execute a shell command. + + Args: + shell (str): The shell to use. + command (str): The command to execute. + + Returns: + subprocess.CompletedProcess: The result of the command execution. + + Raises: + subprocess.CalledProcessError: If the command execution fails. + """ + if validation.is_dangerous_command(command): + logger.warning(f"Potentially dangerous command detected: {command}") + # We still allow execution but log a warning + + try: + if shell == "powershell.exe": + result = subprocess.run([shell, "/c", command], shell=False, check=True) + else: + # Unix: /bin/bash /bin/zsh: uses -c both Ubuntu and macOS should work, others might not + result = subprocess.run([shell, "-c", command], shell=False, check=True) + + logger.debug(f"Command executed successfully: {command}") + return result + except subprocess.CalledProcessError as e: + logger.error(f"Command execution failed: {e}") + raise diff --git a/edison/core/prompt_manager.py b/edison/core/prompt_manager.py new file mode 100644 index 0000000..8f9689d --- /dev/null +++ b/edison/core/prompt_manager.py @@ -0,0 +1,54 @@ +""" +Prompt management for the Edison application. +""" +import os +import logging +from edison.utils import os_utils + +logger = logging.getLogger(__name__) + +def get_prompt_template_path(): + """ + Get the path to the prompt template file. + + Returns: + str: The path to the prompt template file. + """ + script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + return os.path.join(script_dir, "edison.prompt") + +def get_full_prompt(user_prompt, shell): + """ + Construct a full prompt from the template and user input. + + Args: + user_prompt (str): The user's prompt. + shell (str): The shell to use. + + Returns: + str: The full prompt. + """ + # Get the path to the prompt template + prompt_file = get_prompt_template_path() + + try: + # Load the prompt template + with open(prompt_file, "r") as f: + pre_prompt = f.read() + + # Replace placeholders + pre_prompt = pre_prompt.replace("{shell}", shell) + pre_prompt = pre_prompt.replace("{os}", os_utils.get_os_friendly_name()) + + # Append the user prompt + prompt = pre_prompt + user_prompt + + # Make it a question if it's not already + if prompt[-1:] != "?" and prompt[-1:] != ".": + prompt += "?" + + return prompt + except Exception as e: + logger.error(f"Error loading prompt template: {e}") + # Fallback to a simple prompt + return f"Act as a natural language to {shell} command translation engine on {os_utils.get_os_friendly_name()}. {user_prompt}" diff --git a/edison/edison.prompt b/edison/edison.prompt new file mode 100644 index 0000000..80d66e0 --- /dev/null +++ b/edison/edison.prompt @@ -0,0 +1,31 @@ +Act as a natural language to {shell} command translation engine on {os}. + +You are an expert in {shell} on {os} and translate the question at the end to valid syntax. + +Follow these rules: +Construct valid {shell} command that solve the question +Leverage help and man pages to ensure valid syntax and an optimal solution +Be concise +Just show the commands +Return only plaintext +Only show a single answer, but you can always chain commands together +Think step by step +Only create valid syntax (you can use comments if it makes sense) +If python is installed you can use it to solve problems +if python3 is installed you can use it to solve problems +Even if there is a lack of details, attempt to find the most logical solution by going about it step by step +Do not return multiple solutions +Do not show html, styled, colored formatting +Do not creating invalid syntax +Do not add unnecessary text in the response +Do not add notes or intro sentences +Do not show multiple distinct solutions to the question +Do not add explanations on what the commands do +Do not return what the question was +Do not repeat or paraphrase the question in your response +Do not cause syntax errors +Do not rush to a conclusion + +Follow all of the above rules. This is important you MUST follow the above rules. There are no exceptions to these rules. You must always follow them. No exceptions. + +Question: diff --git a/edison/edison.yaml b/edison/edison.yaml new file mode 100644 index 0000000..49331ec --- /dev/null +++ b/edison/edison.yaml @@ -0,0 +1,31 @@ +model: gpt-4o-mini # Default model for command generation +temperature: 0 +max_tokens: 500 + +# Safety: If set to False, commands returned from the AI will be run *without* prompting the user. +safety: True + +# Streaming: If set to True, command generation will be displayed in real-time as tokens arrive. +# Set to False for traditional non-streaming behavior. +streaming: True + +# Show a "Generating command: " prefix during streaming +# Set to False to remove the prefix and only show the command +show_generating_prefix: True + +# UI Configuration: Controls the appearance and behavior of the user interface +ui: + # Enable rich text formatting for command explanations and output + rich_formatting: True + + # Theme for syntax highlighting (options: monokai, github-dark, solarized-dark, etc.) + theme: "monokai" + + # Command display style (options: simple, highlighted, panel) + command_style: "panel" + + # Structured explanations: Generate more structured command explanations + structured_explanations: True + +# Open AI API Key (optional): The key can aso be provided via environment variable (OPENAI_API_KEY), .env, or ~/.openai.apikey file +openai_api_key: diff --git a/edison/ui/__init__.py b/edison/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edison/ui/console.py b/edison/ui/console.py new file mode 100644 index 0000000..7f6678a --- /dev/null +++ b/edison/ui/console.py @@ -0,0 +1,245 @@ +""" +Console UI components for the Edison application. +""" +import logging +import pyperclip +from termcolor import colored +from edison.core import api_client, command_executor +from edison.utils import os_utils, validation, markdown_utils + +logger = logging.getLogger(__name__) + +def handle_streaming_output(token): + """ + Handle a token from the streaming API response. + + Args: + token (str): A token from the streaming response. + """ + # Print the token without a newline and flush immediately + print(token, end='', flush=True) + +def print_command(command, config=None): + """ + Print a command to the console. + + Args: + command (str): The command to print. + config (dict, optional): Configuration dictionary. + """ + if config and config.get("ui", {}).get("rich_formatting", True): + command_style = config.get("ui", {}).get("command_style", "panel") + theme = config.get("ui", {}).get("theme", "monokai") + shell = config.get("shell", "bash") + + if command_style == "panel": + # Use rich panel style + from edison.utils.markdown_utils import print_command_rich + print_command_rich(command, shell=shell, theme=theme) + else: + # Use simple highlighting + print("Command: " + colored(command, 'blue')) + else: + # Use traditional styling + print("Command: " + colored(command, 'blue')) + +def prompt_user_input(config, response): + """ + Prompt the user for input on what to do with the generated command. + + Args: + config (dict): The configuration dictionary. + response (str): The generated command. + + Returns: + str: The user's input. + """ + print_command(response, config) + + if config.get("safety", True): + prompt_text = "Execute command? [Y]es [n]o [m]odify (prompt/command) [c]opy to clipboard ==> " + + if os_utils.missing_posix_display(): + prompt_text = "Execute command? [Y]es [n]o [m]odify (prompt/command) ==> " + + print(prompt_text, end='') + user_input = input() + else: + user_input = "Y" + + return user_input + +def handle_command_execution_streaming(client, config, query, explain=False): + """ + Handle the execution of a command with streaming output. + + Args: + client (OpenAI): The OpenAI client. + config (dict): The configuration dictionary. + query (str): The user's query. + explain (bool): Whether to explain the command. + """ + # Check if we should show the "Generating command: " prefix + if config.get("show_generating_prefix", True): + print(colored("Generating command: ", 'yellow'), end='', flush=True) + + try: + # Call the streaming API + command = api_client.generate_command_streaming(client, config, query, handle_streaming_output) + + # Print a newline after streaming is complete + print() + + # Continue with the regular command execution flow + handle_command_execution(client, config, command, explain) + except KeyboardInterrupt: + print("\nCommand generation cancelled.") + return + except Exception as e: + logger.error(f"Error generating command: {e}") + print(colored(f"\nError generating command: {e}", 'red')) + +def handle_command_execution(client, config, command, explain=False): + """ + Handle the execution of a command based on user input. + + Args: + client (OpenAI): The OpenAI client. + config (dict): The configuration dictionary. + command (str): The command to execute. + explain (bool): Whether to explain the command. + """ + # Check for issues in the response + if validation.check_for_issue(command): + print(colored("There was an issue: " + command, 'red')) + return + + # Check for markdown in the response + if validation.check_for_markdown(command): + print(colored( + "The proposed command contains markdown, response not executed directly: \n", 'red' + ) + command) + return + + # Get explanation if requested + if explain: + try: + from edison.ui.interactive import get_command_explanation + + # Get UI configuration + ui_config = config.get("ui", {}) + structured = ui_config.get("structured_explanations", True) + use_rich = ui_config.get("rich_formatting", True) + + # Generate explanation + explanation = get_command_explanation(client, command, structured=structured) + + if use_rich: + print() # Add a blank line before the explanation + # Format the explanation with rich formatting + formatted_explanation = markdown_utils.format_command_explanation(explanation, use_rich=True) + print(formatted_explanation) + else: + print(colored("\nExplanation:", "green", attrs=["bold"])) + # Format the explanation with terminal-friendly markdown + formatted_explanation = markdown_utils.format_command_explanation(explanation) + print(formatted_explanation) + + print() + except Exception as e: + logger.error(f"Error generating explanation: {e}") + print(colored(f"Error generating explanation: {e}", 'red')) + + # Get user input + user_input = prompt_user_input(config, command) + print() + + # Handle user input + handle_user_input(client, config, user_input, command) + +def handle_user_input(client, config, user_input, command): + """ + Handle user input for command execution. + + Args: + client (OpenAI): The OpenAI client. + config (dict): The configuration dictionary. + user_input (str): The user's input. + command (str): The command to execute. + """ + if user_input.upper() == "Y" or user_input == "": + try: + command_executor.execute_command(config.get("shell", os_utils.get_default_shell()), command) + except Exception as e: + logger.error(f"Error executing command: {e}") + print(colored(f"Error executing command: {e}", 'red')) + + elif user_input.upper() == "M": + print("Modify [p]rompt or [c]ommand? [c] ==> ", end='') + mod_choice = input().lower() + + if mod_choice in ["p", "prompt"]: + # Modify the prompt (natural language description) + print("Modify prompt: ", end='') + modded_query = input() + + if not modded_query.strip(): + print(colored("Empty prompt. Command execution cancelled.", "yellow")) + return + + # Use streaming if enabled in config + if config.get("streaming", True): + try: + handle_command_execution_streaming(client, config, modded_query) + except Exception as e: + logger.error(f"Error generating modified command: {e}") + print(colored(f"Error generating modified command: {e}", 'red')) + else: + try: + modded_response = api_client.generate_command(client, config, modded_query) + handle_command_execution(client, config, modded_response) + except Exception as e: + logger.error(f"Error generating modified command: {e}") + print(colored(f"Error generating modified command: {e}", 'red')) + else: + # Modify the command directly + print("Modify command: ", end='') + # Pre-fill with current command + import readline + readline.set_startup_hook(lambda: readline.insert_text(command)) + try: + modded_command = input() + finally: + readline.set_startup_hook() + + if not modded_command.strip(): + print(colored("Empty command. Command execution cancelled.", "yellow")) + return + + # Print the modified command with styling + print_command(modded_command, config) + + # Ask for confirmation + print("Execute modified command? [Y/n] ==> ", end='') + confirm = input().lower() + + if confirm in ["n", "no"]: + print(colored("Command execution cancelled.", "yellow")) + return + + # Execute the modified command + try: + command_executor.execute_command(config.get("shell", os_utils.get_default_shell()), modded_command) + except Exception as e: + logger.error(f"Error executing modified command: {e}") + print(colored(f"Error executing modified command: {e}", 'red')) + + elif user_input.upper() == "C": + if os_utils.missing_posix_display(): + return + try: + pyperclip.copy(command) + print("Copied command to clipboard.") + except Exception as e: + logger.error(f"Error copying to clipboard: {e}") + print(colored(f"Error copying to clipboard: {e}", 'red')) diff --git a/edison/ui/interactive.py b/edison/ui/interactive.py new file mode 100644 index 0000000..9504a4e --- /dev/null +++ b/edison/ui/interactive.py @@ -0,0 +1,340 @@ +""" +Interactive shell for the Edison application. +""" +import logging +import os +import sys +from termcolor import colored +from prompt_toolkit import PromptSession +from prompt_toolkit.history import FileHistory +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit.lexers import PygmentsLexer +from pygments.lexers.shell import BashLexer +from edison.core import api_client, command_executor +from edison.utils import os_utils, markdown_utils + +logger = logging.getLogger(__name__) + +def handle_streaming_output_interactive(token): + """ + Handle a token from the streaming API response in interactive mode. + + Args: + token (str): A token from the streaming response. + """ + # Print the token without a newline and flush immediately + print(token, end='', flush=True) + +def get_command_explanation(client, command, structured=False): + """ + Get an explanation for a command. + + Args: + client: The OpenAI client. + command (str): The command to explain. + structured (bool): Whether to use structured explanation format. + + Returns: + str: The explanation of the command. + """ + try: + system_prompt = """Explain this shell command in simple terms, breaking down each part:""" + + if structured: + system_prompt = """ + Explain this shell command in a structured format with these sections: + + ## Overview + A brief overview of what the command does + + ## Command Breakdown + Break down each part of the command with explanations + + ## Options/Flags + Explain any options or flags used + + ## Examples + Provide 1-2 simple examples of variations + + ## Cautions + Note any potential issues or cautions + """ + + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": command} + ] + ) + return response.choices[0].message.content + except Exception as e: + logger.error(f"Error getting command explanation: {e}") + return "Could not generate explanation." + +def interactive_mode(client, config): + """ + Start an interactive shell. + + Args: + client: The OpenAI client. + config (dict): The configuration dictionary. + """ + # Create history file path + history_path = os.path.expanduser("~/.edison_history") + history_dir = os.path.dirname(history_path) + os.makedirs(history_dir, exist_ok=True) + + # Create prompt session + history = FileHistory(history_path) + session = PromptSession( + history=history, + auto_suggest=AutoSuggestFromHistory(), + lexer=PygmentsLexer(BashLexer) + ) + + # Print welcome message + print(colored("Edison Interactive Shell", "blue", attrs=["bold"])) + print("Type your queries in natural language.") + print("Type '!help' for help, '!exit' or '!quit' to exit, or press Ctrl+D to exit.") + + while True: + try: + # Get user input + query = session.prompt("edison> ") + + # Handle special commands + if query.strip().lower() in ["!exit", "!quit", "exit", "quit"]: + break + elif query.strip() == "!help": + show_help() + continue + elif not query.strip(): + continue + + # Generate command + try: + # Use streaming if enabled in config + if config.get("streaming", True): + # Check if we should show the "Generating command: " prefix + if config.get("show_generating_prefix", True): + print(colored("Generating command: ", "yellow"), end='', flush=True) + try: + command = api_client.generate_command_streaming( + client, config, query, handle_streaming_output_interactive + ) + print() # Add a newline after streaming is complete + except KeyboardInterrupt: + print("\nCommand generation cancelled.") + continue + else: + print(colored("Generating command...", "yellow")) + command = api_client.generate_command(client, config, query) + + # Print command with appropriate styling based on config + ui_config = config.get("ui", {}) + if ui_config.get("rich_formatting", True): + command_style = ui_config.get("command_style", "panel") + theme = ui_config.get("theme", "monokai") + shell = config.get("shell", "bash") + + if command_style == "panel": + # Use rich panel style + from edison.utils.markdown_utils import print_command_rich + print_command_rich(command, shell=shell, theme=theme) + else: + # Use simple highlighting + print("Command: " + colored(command, 'blue')) + else: + # Use traditional styling + print("Command: " + colored(command, 'blue')) + + # Ask user what to do with the same prompt as non-interactive mode + prompt_text = "Execute command? [Y]es [n]o [m]odify (prompt/command) [c]opy to clipboard [x]plain ==> " + if os_utils.missing_posix_display(): + prompt_text = "Execute command? [Y]es [n]o [m]odify (prompt/command) [x]plain ==> " + + action = session.prompt(prompt_text) + + if action.lower() in ["y", "yes", "e", "execute", ""]: + # Execute command + shell = config.get("shell", os.environ.get("SHELL", "bash")) + try: + command_executor.execute_command(shell, command) + except Exception as e: + logger.error(f"Error executing command: {e}") + print(colored(f"Error executing command: {e}", 'red')) + elif action.lower() in ["m", "modify"]: + # Ask whether to modify prompt or command + mod_choice = session.prompt("Modify [p]rompt or [c]ommand? [c] ==> ") + + if mod_choice.lower() in ["p", "prompt"]: + # Modify the prompt (natural language description) + new_prompt = session.prompt("Modified prompt: ") + + if not new_prompt.strip(): + print(colored("Empty prompt. Command execution cancelled.", "yellow")) + continue + + # Generate new command from modified prompt + print(colored("Generating new command from modified prompt...", "yellow")) + + # Use streaming if enabled + if config.get("streaming", True): + # Check if we should show the "Generating command: " prefix + if config.get("show_generating_prefix", True): + print(colored("Generating command: ", "yellow"), end='', flush=True) + try: + new_command = api_client.generate_command_streaming( + client, config, new_prompt, handle_streaming_output_interactive + ) + print() # Add a newline after streaming is complete + except KeyboardInterrupt: + print("\nCommand generation cancelled.") + continue + else: + new_command = api_client.generate_command(client, config, new_prompt) + + # Print the new command with styling + ui_config = config.get("ui", {}) + if ui_config.get("rich_formatting", True): + command_style = ui_config.get("command_style", "panel") + theme = ui_config.get("theme", "monokai") + shell = config.get("shell", "bash") + + if command_style == "panel": + # Use rich panel style + from edison.utils.markdown_utils import print_command_rich + print_command_rich(new_command, shell=shell, theme=theme) + else: + # Use simple highlighting + print("Command: " + colored(new_command, 'blue')) + else: + # Use traditional styling + print("Command: " + colored(new_command, 'blue')) + + # Ask for confirmation + confirm = session.prompt("Execute this command? [Y/n] ") + if confirm.lower() in ["n", "no"]: + print(colored("Command execution cancelled.", "yellow")) + continue + + # Execute the new command + shell = config.get("shell", os.environ.get("SHELL", "bash")) + try: + command_executor.execute_command(shell, new_command) + except Exception as e: + logger.error(f"Error executing command: {e}") + print(colored(f"Error executing command: {e}", 'red')) + else: + # Modify the command directly + modified_command = session.prompt("Modified command: ", default=command) + + if not modified_command.strip(): + print(colored("Empty command. Command execution cancelled.", "yellow")) + continue + + # Print the modified command with styling + ui_config = config.get("ui", {}) + if ui_config.get("rich_formatting", True): + command_style = ui_config.get("command_style", "panel") + theme = ui_config.get("theme", "monokai") + shell = config.get("shell", "bash") + + if command_style == "panel": + # Use rich panel style + from edison.utils.markdown_utils import print_command_rich + print_command_rich(modified_command, shell=shell, theme=theme) + else: + # Use simple highlighting + print("Command: " + colored(modified_command, 'blue')) + else: + # Use traditional styling + print("Command: " + colored(modified_command, 'blue')) + + # Ask for confirmation + confirm = session.prompt("Execute modified command? [Y/n] ") + if confirm.lower() in ["n", "no"]: + print(colored("Command execution cancelled.", "yellow")) + continue + + # Execute the modified command + shell = config.get("shell", os.environ.get("SHELL", "bash")) + try: + command_executor.execute_command(shell, modified_command) + except Exception as e: + logger.error(f"Error executing command: {e}") + print(colored(f"Error executing command: {e}", 'red')) + elif action.lower() in ["c", "copy"]: + # Copy command to clipboard + if not os_utils.missing_posix_display(): + try: + import pyperclip + pyperclip.copy(command) + print(colored("Command copied to clipboard.", "green")) + except Exception as e: + print(colored(f"Error copying to clipboard: {e}", "red")) + elif action.lower() in ["x", "explain"]: + # Explain command + print(colored("Generating explanation...", "yellow")) + + # Get UI configuration + ui_config = config.get("ui", {}) + structured = ui_config.get("structured_explanations", True) + use_rich = ui_config.get("rich_formatting", True) + + # Generate explanation + explanation = get_command_explanation(client, command, structured=structured) + + if use_rich: + print() # Add a blank line before the explanation + # Format the explanation with rich formatting + formatted_explanation = markdown_utils.format_command_explanation(explanation, use_rich=True) + print(formatted_explanation) + else: + print(colored("\nExplanation:", "green", attrs=["bold"])) + # Format the explanation with terminal-friendly markdown + formatted_explanation = markdown_utils.format_command_explanation(explanation) + print(formatted_explanation) + elif action.lower() in ["n", "no", "s", "skip"]: + # Skip + print(colored("Command skipped.", "yellow")) + else: + # Skip if input not recognized + print(colored("Command skipped - input not recognized.", "yellow")) + + except Exception as e: + logger.error(f"Error: {e}") + print(colored(f"Error: {e}", "red")) + + except KeyboardInterrupt: + continue + except EOFError: + break + + print("Goodbye!") + +def show_help(): + """ + Show help information. + """ + help_text = """ +Edison Interactive Shell Help + +Commands: + !help - Show this help message + !exit, !quit - Exit the interactive shell + exit, quit - Exit the interactive shell + Ctrl+D - Exit the interactive shell + +Actions: + y, yes, - Execute the generated command + n, no - Skip the command + m, modify - Modify the prompt or command before executing + When selected, you'll be asked whether to modify: + - [p]rompt: Change the natural language description + - [c]ommand: Directly edit the generated command + c, copy - Copy the command to clipboard + x, explain - Get an explanation of the command + """ + print(help_text) diff --git a/edison/utils/__init__.py b/edison/utils/__init__.py new file mode 100644 index 0000000..3a8e795 --- /dev/null +++ b/edison/utils/__init__.py @@ -0,0 +1,4 @@ +""" +Utility functions for the Edison application. +""" +from . import markdown_utils \ No newline at end of file diff --git a/edison/utils/logging_utils.py b/edison/utils/logging_utils.py new file mode 100644 index 0000000..3104096 --- /dev/null +++ b/edison/utils/logging_utils.py @@ -0,0 +1,71 @@ +""" +Logging utilities for the Edison application. +""" +import logging +import os +import sys + +def setup_logging(verbose=False): + """ + Configure logging for the application. + + Args: + verbose: If True, enable DEBUG level messages. If False, show only WARNING and above. + + Returns: + logging.Logger: The configured logger. + """ + # Create logs directory if it doesn't exist + script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + logs_dir = os.path.join(script_dir, "logs") + os.makedirs(logs_dir, exist_ok=True) + + # Determine log levels based on verbose flag + # Always save INFO+ logs to file, but console output depends on verbose flag + file_level = logging.INFO + console_level = logging.DEBUG if verbose else logging.WARNING + + # Configure file and console handlers with different levels + file_handler = logging.FileHandler(os.path.join(logs_dir, 'edison.log')) + file_handler.setLevel(file_level) + + console_handler = logging.StreamHandler(sys.stderr) + console_handler.setLevel(console_level) + + # We need to set the root logger level to the lowest of our handlers + root_level = logging.DEBUG if verbose else logging.INFO + + # Basic config with handlers + logging.basicConfig( + level=root_level, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[file_handler, console_handler] + ) + + # Always disable httpx and urllib3 debug logs unless in verbose mode + if not verbose: + logging.getLogger("httpx").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) + + # Get the main application logger + logger = logging.getLogger('edison') + + # Log setup confirmation at appropriate level + if verbose: + logger.debug("Verbose logging enabled (DEBUG level)") + else: + logger.info("Standard logging configuration applied (INFO to file, WARNING to console)") + + return logger + +def get_logger(name): + """ + Get a logger for the specified name. + + Args: + name: The name of the logger. + + Returns: + logging.Logger: The logger. + """ + return logging.getLogger(name) diff --git a/edison/utils/markdown_utils.py b/edison/utils/markdown_utils.py new file mode 100644 index 0000000..9321e74 --- /dev/null +++ b/edison/utils/markdown_utils.py @@ -0,0 +1,216 @@ +""" +Markdown formatting utilities for terminal display. +""" +import re +import io +import sys +from termcolor import colored +from rich.console import Console +from rich.markdown import Markdown +from rich.syntax import Syntax +from rich.panel import Panel + +def convert_markdown_to_terminal(text): + """ + Convert markdown formatting to terminal-friendly colored text. + + Args: + text (str): Markdown text to convert + + Returns: + str: Terminal-formatted text with ANSI color codes + """ + if not text: + return text + + # Handle code blocks with triple backticks + text = re.sub( + r'```(?:\w+)?\n(.*?)\n```', + lambda m: '\n' + colored(m.group(1), 'white', attrs=['bold', 'dark']) + '\n', + text, + flags=re.DOTALL + ) + + # Handle inline code with single backticks + text = re.sub( + r'`([^`]+)`', + lambda m: colored(m.group(1), 'white', attrs=['bold']), + text + ) + + # Handle bold text with ** or __ + text = re.sub( + r'\*\*([^*]+)\*\*|__([^_]+)__', + lambda m: colored(m.group(1) or m.group(2), attrs=['bold']), + text + ) + + # Handle italic text with * or _ + text = re.sub( + r'\*([^*]+)\*|_([^_]+)_', + lambda m: colored(m.group(1) or m.group(2), attrs=['underline']), + text + ) + + # Handle headers + text = re.sub( + r'^#{1,6}\s+(.+)$', + lambda m: colored(m.group(1), 'cyan', attrs=['bold']), + text, + flags=re.MULTILINE + ) + + # Handle unordered lists + text = re.sub( + r'^(\s*[-*•]\s+)(.+)$', + lambda m: m.group(1) + colored(m.group(2), 'white'), + text, + flags=re.MULTILINE + ) + + # Handle ordered lists + text = re.sub( + r'^(\s*\d+\.\s+)(.+)$', + lambda m: m.group(1) + colored(m.group(2), 'white'), + text, + flags=re.MULTILINE + ) + + # Handle links [text](url) - keep only the text part + text = re.sub( + r'\[([^\]]+)\]\([^)]+\)', + lambda m: colored(m.group(1), 'blue', attrs=['underline']), + text + ) + + # Handle horizontal rules + text = re.sub( + r'^-{3,}$|^\*{3,}$|^_{3,}$', + lambda m: colored('-' * 50, 'white', attrs=['dark']), + text, + flags=re.MULTILINE + ) + + # Handle blockquotes + text = re.sub( + r'^>\s+(.+)$', + lambda m: colored('│ ', 'cyan') + colored(m.group(1), 'cyan'), + text, + flags=re.MULTILINE + ) + + return text + +def format_command_explanation(explanation, use_rich=False): + """ + Format a command explanation with appropriate terminal styling. + + Args: + explanation (str): The explanation text in markdown format + use_rich (bool): Whether to use rich formatting + + Returns: + str: Formatted explanation with terminal-friendly styling + """ + if use_rich: + return format_command_explanation_rich(explanation) + + # First convert markdown to terminal format + formatted_text = convert_markdown_to_terminal(explanation) + + # Add additional formatting for common command explanation patterns + + # Highlight command parts and common technical terms + formatted_text = re.sub( + r'\b(command|option|flag|argument|parameter|switch)\b', + lambda m: colored(m.group(0), 'yellow'), + formatted_text, + flags=re.IGNORECASE + ) + + # Highlight file paths and patterns + # More specific pattern to avoid matching things like "Unix/Linux" + formatted_text = re.sub( + r'(?:^|\s)(/(?:[\w.-]+/?)+)(?=\s|$|[,.;:])', # Path must start with / and have at least one directory component + lambda m: colored(m.group(1), 'green'), + formatted_text + ) + + # Highlight command names and utilities + # Common Unix/Linux commands + formatted_text = re.sub( + r'\b(ls|grep|find|awk|sed|cat|cp|mv|rm|mkdir|chmod|chown|ps|kill|top|df|du|tar|gzip|ssh|scp|curl|wget)\b', + lambda m: colored(m.group(0), 'green'), + formatted_text + ) + + # Highlight "This command" starts of sentences + formatted_text = re.sub( + r'(^|[.!?]\s+)(This command|The command)', + lambda m: m.group(1) + colored(m.group(2), 'cyan', attrs=['bold']), + formatted_text + ) + + return formatted_text + +def format_command_explanation_rich(explanation): + """ + Format a command explanation with rich formatting. + + Args: + explanation (str): The explanation text in markdown format + + Returns: + str: Rich-formatted explanation as a string + """ + # Capture the rich output as a string + str_io = io.StringIO() + console = Console(file=str_io, width=80) + + # Create a markdown object + md = Markdown(explanation) + + # Render in a panel with a title + panel = Panel(md, title="Command Explanation", border_style="green") + console.print(panel) + + return str_io.getvalue() + +def format_command_rich(command, shell="bash", theme="monokai"): + """ + Format a command with syntax highlighting using Rich. + + Args: + command (str): The command to format + shell (str): The shell language for syntax highlighting + theme (str): The color theme to use + + Returns: + str: Rich-formatted command as a string + """ + # Capture the rich output as a string + str_io = io.StringIO() + console = Console(file=str_io, width=80) + + # Create a syntax object + syntax = Syntax(command, shell, theme=theme, word_wrap=True) + + # Render in a panel with a title + panel = Panel(syntax, title="Command", border_style="blue") + console.print(panel) + + return str_io.getvalue() + +def print_command_rich(command, shell="bash", theme="monokai"): + """ + Print a command with syntax highlighting using Rich. + + Args: + command (str): The command to print + shell (str): The shell language for syntax highlighting + theme (str): The color theme to use + """ + console = Console() + syntax = Syntax(command, shell, theme=theme, word_wrap=True) + panel = Panel(syntax, title="Command", border_style="blue") + console.print(panel) diff --git a/edison/utils/os_utils.py b/edison/utils/os_utils.py new file mode 100644 index 0000000..8ee15af --- /dev/null +++ b/edison/utils/os_utils.py @@ -0,0 +1,59 @@ +""" +Operating system utilities for the Edison application. +""" +import os +import platform +import subprocess +import distro + +def get_os_friendly_name(): + """ + Returns a friendly name of the user's operating system. + + The function retrieves the current system platform name using the `platform.system()` function. + For Linux, it appends the distribution name retrieved from `distro.name(pretty=True)` to give a + more descriptive representation. For Darwin (Apple's macOS), it appends "macOS" to "Darwin" to + make the output clearer to the user. + + Returns: + str: A friendly name for the user's operating system. It will be one of the following: + - "Linux/" + - "Darwin/macOS" + - The system string returned by `platform.system()` if it's not Linux or Darwin. + """ + os_name = platform.system() + + if os_name == "Linux": + os_name = "Linux/" + distro.name(pretty=True) + elif os_name == "Darwin": + os_name = "Darwin/macOS" + + return os_name + +def missing_posix_display(): + """ + Checks if the DISPLAY environment variable is set in a POSIX-compliant shell. + + This function runs a shell subprocess that outputs the value of the DISPLAY environment + variable. It then checks if this value is unset (i.e., equals a newline 'b'\\n'') in the + current shell environment. If the DISPLAY variable is unset, the function returns `True` + indicating a "missing" display; otherwise, it returns `False`. + + Returns: + bool: `True` if the DISPLAY environment variable is unset or empty, `False` otherwise. + """ + if os.name != "posix": + return False + + display = subprocess.check_output("echo $DISPLAY", shell=True) + return display == b'\n' + +def get_default_shell(): + """ + Get the default shell for the current operating system. + + Returns: + str: The default shell. + """ + # Unix based SHELL (/bin/bash, /bin/zsh), otherwise assuming it's Windows + return os.environ.get("SHELL", "powershell.exe") diff --git a/edison/utils/validation.py b/edison/utils/validation.py new file mode 100644 index 0000000..c131797 --- /dev/null +++ b/edison/utils/validation.py @@ -0,0 +1,60 @@ +""" +Validation utilities for the Edison application. +""" +import re +import logging + +logger = logging.getLogger(__name__) + +def is_dangerous_command(command): + """ + Check if a command contains potentially dangerous operations. + + Args: + command (str): The command to check. + + Returns: + bool: True if the command is potentially dangerous, False otherwise. + """ + dangerous_patterns = [ + r"rm\s+-rf\s+/", # Remove recursively from root + r"sudo\s+rm", # Sudo remove + r"chmod\s+777", # Chmod 777 (too permissive) + r">\s+/dev/", # Redirect to device + r">\s+/etc/", # Redirect to system config + r"mkfs", # Format filesystem + r"dd\s+if=", # Disk destroyer + r":(){:\|:};:", # Fork bomb + ] + + for pattern in dangerous_patterns: + if re.search(pattern, command, re.IGNORECASE): + logger.warning(f"Potentially dangerous command detected: {command}") + return True + + return False + +def check_for_issue(response): + """ + Checks the given response for any issues. + + Args: + response (str): The response to check. + + Returns: + bool: True if there's an issue, False otherwise. + """ + prefixes = ("sorry", "i'm sorry", "the question is not clear", "i'm", "i am") + return response.lower().startswith(prefixes) + +def check_for_markdown(response): + """ + Checks for the presence of markdown formatting in the response. + + Args: + response (str): The response to check. + + Returns: + bool: True if markdown is detected, False otherwise. + """ + return response.count("```", 2) diff --git a/edison_theme_demo.py b/edison_theme_demo.py new file mode 100644 index 0000000..e48152f --- /dev/null +++ b/edison_theme_demo.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Edison theme demonstration script. +This script shows how Edison's theme functionality works with its markdown_utils module. +""" +import sys +import os +import yaml +from rich.console import Console +from rich.table import Table + +# Add the parent directory to the path so we can import Edison modules +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Import Edison's markdown_utils module +from edison.utils.markdown_utils import print_command_rich, format_command_rich + +# Sample commands to demonstrate syntax highlighting +COMMANDS = { + "Simple": "ls -la /var/log", + "Moderate": "find /home -type f -name '*.txt' -size +1M | xargs grep -l 'ERROR' | sort", + "Complex": """#!/bin/bash +# This script processes log files +for file in $(find /var/log -name "*.log" -mtime -1); do + echo "Processing $file..." + if grep -q "ERROR" "$file"; then + echo "Errors found in $file" + grep "ERROR" "$file" | awk '{print $1, $2, $NF}' > "/tmp/$(basename $file).errors" + fi +done""" +} + +def load_edison_config(): + """Load Edison's configuration file.""" + config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "edison", "edison.yaml") + try: + with open(config_path, 'r') as file: + return yaml.safe_load(file) + except Exception as e: + print(f"Error loading Edison configuration: {e}") + return {"ui": {"theme": "monokai"}} + +def main(): + """Main function to demonstrate Edison's theme functionality.""" + console = Console() + + # List of themes to demonstrate + themes = [ + "monokai", + "github-dark", + "solarized-dark", + "dracula", + "nord", + "gruvbox-dark", + ] + + # Print header + console.print("\n[bold cyan]Edison Theme Integration Demonstration[/bold cyan]") + console.print("This shows how Edison's theme functionality works with its markdown_utils module.\n") + + # Load Edison's configuration + edison_config = load_edison_config() + current_theme = edison_config.get("ui", {}).get("theme", "monokai") + + console.print(f"[bold]Current Edison theme:[/bold] [green]{current_theme}[/green]\n") + + # Display theme usage examples + console.print("[bold]1. How Edison uses themes in the code:[/bold]") + console.print(""" +# In edison/utils/markdown_utils.py: +def print_command_rich(command, shell="bash", theme="monokai"): + console = Console() + syntax = Syntax(command, shell, theme=theme, word_wrap=True) + panel = Panel(syntax, title="Command", border_style="blue") + console.print(panel) + +# In edison/ui/console.py: +def print_command(command, config=None): + if config and config.get("ui", {}).get("rich_formatting", True): + command_style = config.get("ui", {}).get("command_style", "panel") + theme = config.get("ui", {}).get("theme", "monokai") + shell = config.get("shell", "bash") + + if command_style == "panel": + from edison.utils.markdown_utils import print_command_rich + print_command_rich(command, shell=shell, theme=theme) + """) + + console.print("\n[bold]2. Theme comparison with different commands:[/bold]\n") + + # For each complexity level, show the command with different themes + for complexity, command in COMMANDS.items(): + console.print(f"[bold yellow]Command Complexity: {complexity}[/bold yellow]") + console.print(f"[dim]{command}[/dim]\n") + + for theme in themes: + console.print(f"[bold]Theme: [green]{theme}[/green][/bold]") + print_command_rich(command, theme=theme) + console.print() + + if complexity != list(COMMANDS.keys())[-1]: + console.print("[italic]Press Enter to see the next command complexity...[/italic]") + input() + + # Show how to use Edison's theme functionality in custom code + console.print("\n[bold]3. How to use Edison's theme functionality in your own code:[/bold]") + console.print(""" +# Import Edison's markdown_utils module +from edison.utils.markdown_utils import print_command_rich + +# Print a command with a specific theme +print_command_rich("ls -la", theme="github-dark") + +# Or get the formatted string to use elsewhere +from edison.utils.markdown_utils import format_command_rich +formatted = format_command_rich("grep -r 'pattern' .", theme="nord") +print(formatted) + """) + +if __name__ == "__main__": + main() diff --git a/install_edison.bat b/install_edison.bat new file mode 100644 index 0000000..e9e285f --- /dev/null +++ b/install_edison.bat @@ -0,0 +1,22 @@ +@echo off +REM Installation script for Edison on Windows + +echo Creating virtual environment... +python -m venv venv + +echo Activating virtual environment... +call venv\Scripts\activate.bat + +echo Installing Edison... +pip install -e . + +echo Creating logs directory... +mkdir edison\logs + +echo. +echo Installation complete! +echo You can now use Edison by running: edison +echo For example: edison how to list all files in the current directory +echo. +echo To uninstall, simply delete this directory and remove the venv. +pause diff --git a/install_edison.sh b/install_edison.sh new file mode 100755 index 0000000..f9b7ee5 --- /dev/null +++ b/install_edison.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Installation script for Edison + +# Create virtual environment +echo "Creating virtual environment..." +python -m venv venv + +# Activate virtual environment +echo "Activating virtual environment..." +source venv/bin/activate + +# Install package +echo "Installing Edison..." +pip install -e . + +# Create logs directory +mkdir -p edison/logs + +echo "Installation complete!" +echo "You can now use Edison by running: edison " +echo "For example: edison how to list all files in the current directory" +echo "" +echo "To uninstall, simply delete this directory and remove the venv." diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..9f38c60 --- /dev/null +++ b/pylintrc @@ -0,0 +1,499 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ed1f60d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +openai>=1.0.0 +termcolor==2.2.0 +colorama==0.4.4 +python-dotenv==1.0.0 +distro==1.7.0 +PyYAML==6.0.2 +pyperclip==1.8.2 +prompt_toolkit>=3.0.0 +pygments>=2.10.0 +rich>=10.12.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7caf81c --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +""" +Setup script for the Edison package. +""" +from setuptools import setup, find_packages + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="edison", + version="0.1.0", + author="Edison Team", + author_email="example@example.com", + description="AI bot that translates natural language to shell commands", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/yourusername/edison", + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires=">=3.6", + install_requires=[ + "openai>=1.0.0", + "termcolor==2.2.0", + "colorama==0.4.4", + "python-dotenv==1.0.0", + "distro==1.7.0", + "PyYAML==6.0.2", + "pyperclip==1.8.2", + "prompt_toolkit==3.0.50", + ], + entry_points={ + "console_scripts": [ + "edison=edison.cli:main", + ], + }, + include_package_data=True, + package_data={ + "edison": ["edison.prompt", "edison.yaml"], + }, +) diff --git a/theme_demo.py b/theme_demo.py new file mode 100644 index 0000000..336fb80 --- /dev/null +++ b/theme_demo.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +Theme demonstration for Edison's rich output formatting. +This script shows the same command rendered with different syntax highlighting themes. +""" +import sys +from rich.console import Console +from rich.syntax import Syntax +from rich.panel import Panel +from rich.columns import Columns +from rich.table import Table + +# Sample bash command with various syntax elements to highlight theme differences +SAMPLE_COMMAND = """#!/bin/bash + +# This is a comment +echo "Hello, world!" + +# Define variables +name="User" +count=10 + +# Conditional statement +if [ "$count" -gt 5 ]; then + echo "Count is greater than 5" + for ((i=0; i<$count; i++)); do + echo "Processing item $i for $name" + if [ -f "/tmp/file_$i.txt" ]; then + rm "/tmp/file_$i.txt" + fi + done +else + echo "Count is less than or equal to 5" +fi + +# Function definition +function process_files() { + local dir="$1" + find "$dir" -type f -name "*.txt" | while read file; do + echo "Found file: $file" + grep -q "ERROR" "$file" && echo "Error found in $file" + done +} + +# Call the function +process_files "/var/log" +""" + +def show_theme(theme_name): + """Display a command with the specified theme.""" + syntax = Syntax( + SAMPLE_COMMAND, + "bash", + theme=theme_name, + line_numbers=True, + word_wrap=True, + ) + panel = Panel( + syntax, + title=f"Theme: {theme_name}", + border_style="blue", + padding=(1, 2), + ) + return panel + +def main(): + """Main function to display theme comparisons.""" + console = Console() + + # List of themes to demonstrate + themes = [ + "monokai", + "github-dark", + "solarized-dark", + "dracula", + "nord", + "gruvbox-dark", + "one-dark", + "vs", + "solarized-light", + "gruvbox-light", + ] + + # Print header + console.print("\n[bold cyan]Edison Theme Demonstration[/bold cyan]") + console.print("This shows the same bash script rendered with different syntax highlighting themes.\n") + + # Display theme comparison table + table = Table(title="Available Themes for Edison") + table.add_column("Theme Name", style="cyan") + table.add_column("Description", style="green") + + theme_descriptions = { + "monokai": "Default theme with vibrant colors (dark)", + "github-dark": "Based on GitHub's dark theme", + "solarized-dark": "Popular dark theme with softer colors", + "dracula": "High contrast dark theme", + "nord": "Bluish dark theme", + "gruvbox-dark": "Retro dark theme with warm colors", + "one-dark": "Based on Atom's One Dark theme", + "vs": "Based on Visual Studio's default theme (light)", + "solarized-light": "Light version of the Solarized theme", + "gruvbox-light": "Light version of the Gruvbox theme", + } + + for theme in themes: + table.add_row(theme, theme_descriptions.get(theme, "")) + + console.print(table) + console.print() + + # Display each theme one by one + for theme in themes: + console.print(show_theme(theme)) + console.print() + + # Optional: pause between themes for better viewing + if theme != themes[-1]: + console.print("[italic]Press Enter to see the next theme...[/italic]") + input() + +if __name__ == "__main__": + main()