Initial commit

This commit is contained in:
2025-04-09 09:34:15 +02:00
commit c19fb93ec5
47 changed files with 5174 additions and 0 deletions
+340
View File
@@ -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, <enter> - 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)