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)
This commit is contained in:
Hardik Zinzuvadiya 2026-03-15 18:05:11 +05:30
parent 9b4b5236b2
commit 043fa0e7b7

View file

@ -121,6 +121,7 @@ def show_help():
(" ─────────────────────────────────────\n", "dim"),
(" 120 ", "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]",