Files
shell-history-automation-tool/shellhist/cli/search.py
7000pctAUTO 4add24159d
Some checks failed
CI / test (push) Has been cancelled
Initial upload with CI/CD workflow
2026-01-31 13:13:07 +00:00

128 lines
3.3 KiB
Python

"""Search command for the shell history tool."""
import os
from typing import Optional
import click
from rich.console import Console
from rich.table import Table
from shellhist.core import HistoryLoader
from shellhist.core.search import fuzzy_search
from shellhist.utils import format_timestamp
@click.command("search")
@click.argument("query", type=str)
@click.option(
"--history",
"-H",
type=str,
help="Path to history file",
)
@click.option(
"--threshold",
"-t",
type=int,
default=70,
help="Minimum similarity threshold (0-100, default: 70)",
)
@click.option(
"--limit",
"-l",
type=int,
default=20,
help="Maximum number of results (default: 20)",
)
@click.option(
"--reverse/--no-reverse",
"-r",
default=False,
help="Sort by recency (newest first)",
)
@click.option(
"--recent/--no-recent",
default=False,
help="Boost scores for recent commands (last 24h)",
)
@click.option(
"--shell",
"-s",
type=click.Choice(["bash", "zsh"]),
help="Shell type for parsing",
)
@click.pass_context
def search_command(
ctx: click.Context,
query: str,
history: Optional[str],
threshold: int,
limit: int,
reverse: bool,
recent: bool,
shell: Optional[str],
) -> None:
"""Search shell history with fuzzy matching.
QUERY is the search string to match against your command history.
Examples:
\b
shellhist search "git commit"
shellhist search "npm run" --threshold 80 --limit 10
shellhist search "docker ps" --recent
"""
console = Console()
try:
if shell:
os.environ["SHELL"] = f"/bin/{shell}"
loader = HistoryLoader(history_path=history)
store = loader.load()
if not store.entries:
console.print("[yellow]No entries found in history.[/yellow]")
return
results = fuzzy_search(
store=store,
query=query,
threshold=threshold,
limit=limit,
reverse=reverse,
recent=recent,
)
if not results:
console.print(f"[yellow]No commands found matching '{query}' with threshold {threshold}[/yellow]")
return
table = Table(show_header=True, header_style="bold magenta")
table.add_column("#", width=4)
table.add_column("Match %", width=8)
table.add_column("Command", width=60)
table.add_column("Last Used", width=20)
for i, (entry, score) in enumerate(results, 1):
timestamp = format_timestamp(entry.timestamp)
table.add_row(
str(i),
f"{score}%",
entry.command[:58] + ".." if len(entry.command) > 60 else entry.command,
timestamp,
)
console.print(table)
total_unique = len(store.get_unique_commands())
console.print(f"\n[dim]Found {len(results)} matches from {total_unique} unique commands[/dim]")
except FileNotFoundError as e:
console.print(f"[red]Error: {e}[/red]")
ctx.exit(1)
except Exception as e:
console.print(f"[red]Error searching history: {e}[/red]")
ctx.exit(1)