- Add return type annotations to __hash__ (-> int) and __eq__ (-> bool) in HistoryEntry - Add TextIO import and type annotations for file parameters - Add type ignore comment for fuzzywuzzy import - Add HistoryEntry import and list type annotations in time_analysis - Add assert statements for Optional[datetime] timestamps - Add TypedDict classes for type-safe pattern dictionaries - Add CommandPattern import and list[CommandPattern] type annotation - Add -> None return types to all test methods - Remove unused HistoryEntry import (F401)
160 lines
4.3 KiB
Python
160 lines
4.3 KiB
Python
"""Alias suggestion command."""
|
|
|
|
import os
|
|
from typing import Optional
|
|
|
|
import click
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
|
|
from shellhist.core import HistoryLoader
|
|
from shellhist.core.patterns import (
|
|
detect_command_pairs,
|
|
detect_command_triplets,
|
|
detect_repetitive_commands,
|
|
)
|
|
|
|
|
|
@click.command("suggest-aliases")
|
|
@click.option(
|
|
"--history",
|
|
"-H",
|
|
type=str,
|
|
help="Path to history file",
|
|
)
|
|
@click.option(
|
|
"--auto-create",
|
|
"-a",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Automatically create aliases without prompting",
|
|
)
|
|
@click.option(
|
|
"--dry-run",
|
|
"-d",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Show what would be created without making changes",
|
|
)
|
|
@click.option(
|
|
"--shell",
|
|
"-s",
|
|
type=click.Choice(["bash", "zsh"]),
|
|
help="Shell type for parsing",
|
|
)
|
|
@click.pass_context
|
|
def alias_command(
|
|
ctx: click.Context,
|
|
history: Optional[str],
|
|
auto_create: bool,
|
|
dry_run: bool,
|
|
shell: Optional[str],
|
|
) -> None:
|
|
"""Generate alias suggestions for detected patterns.
|
|
|
|
Examples:
|
|
|
|
\b
|
|
shellhist suggest-aliases
|
|
shellhist suggest-aliases --auto-create
|
|
shellhist suggest-aliases --dry-run
|
|
"""
|
|
console = Console()
|
|
|
|
try:
|
|
loader = HistoryLoader(history_path=history)
|
|
store = loader.load()
|
|
|
|
if not store.entries:
|
|
console.print("[yellow]No entries found in history.[/yellow]")
|
|
return
|
|
|
|
pairs = detect_command_pairs(store, min_frequency=2)
|
|
triplets = detect_command_triplets(store, min_frequency=2)
|
|
|
|
sequences = [
|
|
(p.commands, p.frequency)
|
|
for p in pairs + triplets
|
|
if len(p.commands) >= 2
|
|
]
|
|
|
|
if not sequences:
|
|
console.print(
|
|
"[yellow]No command sequences found for alias suggestions.[/yellow]"
|
|
)
|
|
console.print("Try running more commands first.")
|
|
return
|
|
|
|
console.print(f"\n[bold cyan]Detected Command Sequences[/bold cyan]")
|
|
|
|
for i, (cmds, freq) in enumerate(sequences[:10], 1):
|
|
seq = " && ".join(cmds)
|
|
console.print(f"{i}. {seq} (frequency: {freq})")
|
|
|
|
console.print("\n[bold cyan]Suggested Aliases[/bold cyan]")
|
|
|
|
for i, (cmds, freq) in enumerate(sequences[:10], 1):
|
|
alias_name = generate_alias_name(cmds)
|
|
alias_cmd = " && ".join(cmds)
|
|
alias_str = f"alias {alias_name}='{alias_cmd}'"
|
|
|
|
panel = Panel(
|
|
f"[green]{alias_str}[/green]",
|
|
title=f"Alias {i}",
|
|
expand=False,
|
|
)
|
|
console.print(panel)
|
|
|
|
if not dry_run:
|
|
if auto_create:
|
|
append_to_shell_file(alias_str)
|
|
console.print(f" [green]Created alias '{alias_name}'[/green]")
|
|
else:
|
|
if click.confirm(f" Create this alias?"):
|
|
append_to_shell_file(alias_str)
|
|
console.print(f" [green]Created alias '{alias_name}'[/green]")
|
|
|
|
except FileNotFoundError as e:
|
|
console.print(f"[red]Error: {e}[/red]")
|
|
ctx.exit(1)
|
|
except Exception as e:
|
|
console.print(f"[red]Error generating aliases: {e}[/red]")
|
|
ctx.exit(1)
|
|
|
|
|
|
def generate_alias_name(commands: list[str]) -> str:
|
|
"""Generate a meaningful alias name from command list."""
|
|
if not commands:
|
|
return "custom"
|
|
|
|
keywords = []
|
|
for cmd in commands:
|
|
parts = cmd.split()
|
|
if parts:
|
|
keywords.append(parts[0])
|
|
|
|
keywords = [k for k in keywords if k not in ("sudo", "do", "run", "exec")]
|
|
|
|
if not keywords:
|
|
return "custom"
|
|
|
|
base = "".join(keywords[:3]).lower()
|
|
|
|
if len(base) > 12:
|
|
base = base[:12]
|
|
|
|
return base
|
|
|
|
|
|
def append_to_shell_file(alias_str: str) -> None:
|
|
"""Append alias to shell config file."""
|
|
shell = os.environ.get("SHELL", "")
|
|
|
|
if "zsh" in shell:
|
|
rc_file = os.path.expanduser("~/.zshrc")
|
|
else:
|
|
rc_file = os.path.expanduser("~/.bashrc")
|
|
|
|
with open(rc_file, "a", encoding="utf-8") as f:
|
|
f.write(f"\n{alias_str}\n")
|