Initial upload: shell-speak CLI tool with natural language to shell command conversion
This commit is contained in:
218
shell_speak/main.py
Normal file
218
shell_speak/main.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""Main CLI entry point for shell-speak."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import typer
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from shell_speak import __version__
|
||||
from shell_speak.config import DEFAULT_TOOLS, ensure_data_dir, get_history_file
|
||||
from shell_speak.history import get_history_manager
|
||||
from shell_speak.interactive import run_interactive_mode
|
||||
from shell_speak.library import get_loader
|
||||
from shell_speak.matcher import get_matcher
|
||||
from shell_speak.output import (
|
||||
console,
|
||||
display_command,
|
||||
display_error,
|
||||
display_history,
|
||||
display_info,
|
||||
)
|
||||
from shell_speak.models import CommandMatch
|
||||
|
||||
app = typer.Typer(
|
||||
name="shell-speak",
|
||||
add_completion=False,
|
||||
help="Convert natural language to shell commands",
|
||||
)
|
||||
|
||||
|
||||
def version_callback(value: bool):
|
||||
"""Show version information."""
|
||||
if value:
|
||||
console.print(f"Shell Speak v{__version__}")
|
||||
raise typer.Exit()
|
||||
|
||||
|
||||
@app.callback()
|
||||
def main(
|
||||
version: bool = typer.Option(
|
||||
False,
|
||||
"--version",
|
||||
"-V",
|
||||
callback=version_callback,
|
||||
is_eager=True,
|
||||
help="Show version information",
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
@app.command()
|
||||
def convert(
|
||||
query: str = typer.Argument(..., help="Natural language description of the command"),
|
||||
tool: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--tool",
|
||||
"-t",
|
||||
help=f"Filter by tool: {', '.join(DEFAULT_TOOLS)}",
|
||||
),
|
||||
explain: bool = typer.Option(
|
||||
False,
|
||||
"--explain",
|
||||
"-e",
|
||||
help="Show detailed explanation of the command",
|
||||
),
|
||||
dry_run: bool = typer.Option(
|
||||
False,
|
||||
"--dry-run",
|
||||
"-n",
|
||||
help="Preview the command without executing",
|
||||
),
|
||||
):
|
||||
"""Convert natural language to a shell command."""
|
||||
ensure_data_dir()
|
||||
|
||||
matcher = get_matcher()
|
||||
match = matcher.match(query, tool)
|
||||
|
||||
if match:
|
||||
display_command(match, explain=explain)
|
||||
|
||||
if dry_run:
|
||||
display_info("Dry run - command not executed")
|
||||
else:
|
||||
display_info("Use --dry-run to preview without execution")
|
||||
else:
|
||||
display_error(f"Could not find a matching command for: '{query}'")
|
||||
display_info("Try using --tool to specify which tool you're using")
|
||||
|
||||
|
||||
@app.command()
|
||||
def interactive(
|
||||
interactive_mode: bool = typer.Option(
|
||||
False,
|
||||
"--interactive",
|
||||
"-i",
|
||||
is_eager=True,
|
||||
help="Enter interactive mode",
|
||||
),
|
||||
):
|
||||
"""Enter interactive mode with history and auto-completion."""
|
||||
run_interactive_mode()
|
||||
|
||||
|
||||
@app.command()
|
||||
def history(
|
||||
limit: int = typer.Option(
|
||||
20,
|
||||
"--limit",
|
||||
"-l",
|
||||
help="Number of entries to show",
|
||||
),
|
||||
tool: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--tool",
|
||||
"-t",
|
||||
help=f"Filter by tool: {', '.join(DEFAULT_TOOLS)}",
|
||||
),
|
||||
search: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--search",
|
||||
"-s",
|
||||
help="Search history for query",
|
||||
),
|
||||
):
|
||||
"""View command history."""
|
||||
ensure_data_dir()
|
||||
history_manager = get_history_manager()
|
||||
history_manager.load()
|
||||
|
||||
if search:
|
||||
entries = history_manager.search(search, tool)
|
||||
else:
|
||||
entries = history_manager.get_recent(limit)
|
||||
|
||||
if entries:
|
||||
display_history(entries, limit)
|
||||
else:
|
||||
display_info("No history entries found")
|
||||
|
||||
|
||||
@app.command()
|
||||
def learn(
|
||||
query: str = typer.Argument(..., help="The natural language query"),
|
||||
command: str = typer.Argument(..., help="The shell command to associate"),
|
||||
tool: str = typer.Option(
|
||||
"custom",
|
||||
"--tool",
|
||||
"-t",
|
||||
help=f"Tool category: {', '.join(DEFAULT_TOOLS)}",
|
||||
),
|
||||
):
|
||||
"""Learn a new command pattern from your correction."""
|
||||
ensure_data_dir()
|
||||
loader = get_loader()
|
||||
loader.load_library()
|
||||
loader.add_correction(query, command, tool)
|
||||
display_info(f"Learned: '{query}' -> '{command}'")
|
||||
|
||||
|
||||
@app.command()
|
||||
def forget(
|
||||
query: str = typer.Argument(..., help="The query to forget"),
|
||||
tool: str = typer.Option(
|
||||
"custom",
|
||||
"--tool",
|
||||
"-t",
|
||||
help="Tool category",
|
||||
),
|
||||
):
|
||||
"""Forget a learned pattern."""
|
||||
ensure_data_dir()
|
||||
loader = get_loader()
|
||||
loader.load_library()
|
||||
|
||||
if loader.remove_correction(query, tool):
|
||||
display_info(f"Forgot pattern for: '{query}'")
|
||||
else:
|
||||
display_error(f"Pattern not found: '{query}'")
|
||||
|
||||
|
||||
@app.command()
|
||||
def reload():
|
||||
"""Reload command libraries and corrections."""
|
||||
ensure_data_dir()
|
||||
loader = get_loader()
|
||||
loader.reload()
|
||||
display_info("Command libraries reloaded")
|
||||
|
||||
|
||||
@app.command()
|
||||
def tools():
|
||||
"""List available tools."""
|
||||
console.print(Panel(
|
||||
Text("Available Tools", justify="center", style="bold cyan"),
|
||||
expand=False,
|
||||
))
|
||||
for tool in DEFAULT_TOOLS:
|
||||
console.print(f" [tool]{tool}[/]")
|
||||
|
||||
|
||||
def main_entry():
|
||||
"""Entry point for the CLI."""
|
||||
try:
|
||||
app()
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[info]Interrupted.[/]")
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
display_error(f"An error occurred: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_entry()
|
||||
Reference in New Issue
Block a user