Commit 043fa0e7 authored by Hardik Zinzuvadiya's avatar Hardik Zinzuvadiya
Browse files

Feature: Search tools by name or keyword (/ or s)

- Add _collect_all_tools() — walks all collections recursively and
  returns (tool_instance, category_name) pairs for 185 tools
- Add search_tools() — prompts for query, matches against TITLE and
  DESCRIPTION (case-insensitive), shows results table with category,
  user selects a result number to jump directly into tool.show_options()
- Wire / and s commands into interact_menu()
- Add / search to help overlay and hint bar
- Fix ToolManager row number: was hardcoded 18, now computed dynamically
  from len(categories) + 1 (currently 21)
parent 9b4b5236
Loading
Loading
Loading
Loading
+83 −2
Original line number Diff line number Diff line
@@ -121,6 +121,7 @@ def show_help():
            ("  ─────────────────────────────────────\n", "dim"),
            ("  1–20   ", "bold cyan"), ("open a category\n", "white"),
            ("  21     ", "bold cyan"), ("Update / Uninstall hackingtool\n", "white"),
            ("  / or s ", "bold cyan"), ("search tools by name or keyword\n", "white"),
            ("  ?      ", "bold cyan"), ("show this help\n", "white"),
            ("  q      ", "bold cyan"), ("quit hackingtool\n\n", "white"),
            ("  Inside a category\n", "bold white"),
@@ -305,20 +306,96 @@ def build_menu():
    ))

    # ── ToolManager row ──
    tm_num = len(categories) + 1
    console.print(
        f"  [bold magenta]  18[/bold magenta]  {update_def[1]}  "
        f"  [bold magenta]  {tm_num}[/bold magenta]  {update_def[1]}  "
        f"[magenta]{update_def[2]}[/magenta]"
    )

    # ── Hint bar ──
    console.print(Rule(style="dim magenta"))
    console.print(
        "  [dim]Enter number to open  ·  "
        "  [dim]Enter number  ·  "
        "[bold cyan]/[/bold cyan] search  ·  "
        "[bold cyan]?[/bold cyan] help  ·  "
        "[bold cyan]q[/bold cyan] quit[/dim]\n"
    )


# ── Search ─────────────────────────────────────────────────────────────────────

def _collect_all_tools() -> list[tuple]:
    """Walk all collections and return (tool_instance, category_name) pairs."""
    from core import HackingTool, HackingToolsCollection
    results = []

    def _walk(items, parent_title=""):
        for item in items:
            if isinstance(item, HackingToolsCollection):
                _walk(item.TOOLS, item.TITLE)
            elif isinstance(item, HackingTool):
                results.append((item, parent_title))

    _walk(all_tools)
    return results


def search_tools():
    """Interactive search — user types query, results update, select to jump."""
    query = Prompt.ask("[bold cyan]/ Search[/bold cyan]", default="").strip().lower()
    if not query:
        return

    all_tool_list = _collect_all_tools()

    # Match against title + description
    matches = []
    for tool, category in all_tool_list:
        title = (tool.TITLE or "").lower()
        desc = (tool.DESCRIPTION or "").lower()
        if query in title or query in desc:
            matches.append((tool, category))

    if not matches:
        console.print(f"[dim]No tools found matching '{query}'[/dim]")
        Prompt.ask("[dim]Press Enter to return[/dim]", default="")
        return

    # Display results
    table = Table(
        title=f"Search results for '{query}'",
        box=box.SIMPLE_HEAD, show_lines=True,
    )
    table.add_column("No.", justify="center", style="bold cyan", width=5)
    table.add_column("Tool", style="bold yellow", min_width=20)
    table.add_column("Category", style="magenta", min_width=15)
    table.add_column("Description", style="white", overflow="fold")

    for i, (tool, cat) in enumerate(matches, start=1):
        desc = (tool.DESCRIPTION or "").splitlines()[0]
        table.add_row(str(i), tool.TITLE, cat, desc)

    table.add_row("99", "Back to main menu", "", "")
    console.print(table)

    raw = Prompt.ask("[bold cyan]>[/bold cyan]", default="").strip().lower()
    if not raw or raw == "99":
        return

    try:
        idx = int(raw)
    except ValueError:
        return

    if 1 <= idx <= len(matches):
        tool, cat = matches[idx - 1]
        console.print(Panel(
            f"[bold magenta]{tool.TITLE}[/bold magenta]  [dim]({cat})[/dim]",
            border_style="magenta", box=box.ROUNDED,
        ))
        tool.show_options()


# ── Main interaction loop ──────────────────────────────────────────────────────

def interact_menu():
@@ -334,6 +411,10 @@ def interact_menu():
                show_help()
                continue

            if raw.startswith("/") or raw in ("s", "search"):
                search_tools()
                continue

            if raw in ("q", "quit", "exit"):
                console.print(Panel(
                    "[bold white on magenta]  Goodbye — Come Back Safely  [/bold white on magenta]",