Initial commit
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user