diff --git a/src/hatiyar/cli/commands.py b/src/hatiyar/cli/commands.py index a805785..9a7e088 100644 --- a/src/hatiyar/cli/commands.py +++ b/src/hatiyar/cli/commands.py @@ -479,18 +479,20 @@ def handle_show(args: List[str], console, session: CLISession) -> None: if not args: console.print("[red]Usage: show [/red]") console.print( - "[dim]Available: [cyan]show options[/cyan], [cyan]show global[/cyan][/dim]" + "[dim]Available: [cyan]show options[/cyan], [cyan]show operations[/cyan], [cyan]show global[/cyan][/dim]" ) return if args[0] == "options": show_module_options(console, session) + elif args[0] == "operations": + show_module_operations(console, session) elif args[0] == "global": show_global_options(console, session) else: console.print(f"[red]Unknown show target:[/red] {args[0]}") console.print( - "[dim]Available: [cyan]show options[/cyan], [cyan]show global[/cyan][/dim]" + "[dim]Available: [cyan]show options[/cyan], [cyan]show operations[/cyan], [cyan]show global[/cyan][/dim]" ) @@ -570,6 +572,17 @@ def show_module_options(console, session: CLISession) -> None: ) +def show_module_operations(console, session: CLISession) -> None: + if not session.active_module: + console.print("[red]No module loaded.[/red]") + return + + if hasattr(session.active_module, "show_operations"): + session.active_module.show_operations() + else: + console.print("[dim]This module does not have operations to display[/dim]") + + def handle_run(console, session: CLISession) -> None: if not session.active_module: console.print("[red]✗ No module loaded[/red]") diff --git a/src/hatiyar/core/modules.py b/src/hatiyar/core/modules.py index 719d0cc..244ccb4 100644 --- a/src/hatiyar/core/modules.py +++ b/src/hatiyar/core/modules.py @@ -382,7 +382,7 @@ def search_modules(self, query: str) -> List[Dict[str, Any]]: query_lower = query.lower() results = [] - search_fields = ["name", "description", "cve", "category", "author"] + search_fields = ["path", "name", "description", "cve", "category", "author"] for metadata in self.metadata_cache.values(): if any( diff --git a/src/hatiyar/main.py b/src/hatiyar/main.py index 0b0a62e..e279752 100644 --- a/src/hatiyar/main.py +++ b/src/hatiyar/main.py @@ -208,19 +208,31 @@ def search( console.print(f"[yellow]✗ No results for:[/yellow] {query}") return - table = Table(title=f"[bold]Search:[/bold] {query}", title_style="cyan") + # Check if any results have CVE IDs + has_cve = any(mod.get("cve_id") or mod.get("cve") for mod in results) + + # Build table structure + table = Table(title=f"[bold]Results:[/bold] {query}", title_style="cyan") table.add_column("#", width=4, justify="right", style="dim") table.add_column("Path", style="green", width=25) table.add_column("Name", style="cyan bold") - table.add_column("CVE", style="red") + if has_cve: + table.add_column("CVE", style="red") + + # Populate table rows for idx, mod in enumerate(results, 1): - table.add_row( + row_data = [ str(idx), mod.get("path", "N/A"), mod.get("name", "N/A"), - mod.get("cve_id", "-"), - ) + ] + + if has_cve: + cve_id = mod.get("cve_id") or mod.get("cve", "-") + row_data.append(cve_id) + + table.add_row(*row_data) console.print(table) console.print(f"\n[dim]✓ Found {len(results)} modules[/dim]\n") diff --git a/src/hatiyar/modules/misc/encoders.py b/src/hatiyar/modules/misc/encoders.py new file mode 100644 index 0000000..2a1ddaf --- /dev/null +++ b/src/hatiyar/modules/misc/encoders.py @@ -0,0 +1,140 @@ +"""Encoding/Decoding utilities""" + +import base64 +from urllib.parse import quote, unquote +from rich.console import Console +from hatiyar.core.module_base import ModuleBase + +console = Console() + + +class Module(ModuleBase): + """Encode/decode strings and generate UUIDs""" + + NAME = "Encoders" + DESCRIPTION = "Encode and decode strings\n\nOperations: base64_encode, base64_decode, url_encode, url_decode" + AUTHOR = "hatiyar" + CATEGORY = "misc" + + OPTIONS = { + "INPUT": "", + "OPERATION": "base64_encode", # base64_encode, base64_decode, url_encode, url_decode + } + + REQUIRED_OPTIONS = [] + + def run(self) -> dict: + operation = self.options.get("OPERATION", "base64_encode").lower() + input_data = self.options.get("INPUT", "") + + handlers = { + "base64_encode": lambda: self._b64_encode(input_data), + "base64_decode": lambda: self._b64_decode(input_data), + "url_encode": lambda: self._url_encode(input_data), + "url_decode": lambda: self._url_decode(input_data), + } + + handler = handlers.get(operation) + if not handler: + return {"success": False, "error": f"Unknown operation: {operation}"} + + try: + return handler() + except Exception as e: + return {"success": False, "error": str(e)} + + def _b64_encode(self, data: str) -> dict: + if not data: + return {"success": False, "error": "INPUT required"} + + encoded = base64.b64encode(data.encode()).decode() + console.print(f"[green]base64:[/green] {encoded}") + return { + "success": True, + "operation": "base64_encode", + "input": data, + "output": encoded, + } + + def _b64_decode(self, data: str) -> dict: + if not data: + return {"success": False, "error": "INPUT required"} + + try: + decoded = base64.b64decode(data).decode() + console.print(f"[green]decoded:[/green] {decoded}") + return { + "success": True, + "operation": "base64_decode", + "input": data, + "output": decoded, + } + except Exception as e: + return {"success": False, "error": f"Invalid base64: {str(e)}"} + + def _url_encode(self, data: str) -> dict: + if not data: + return {"success": False, "error": "INPUT required"} + + encoded = quote(data) + console.print(f"[green]encoded:[/green] {encoded}") + return { + "success": True, + "operation": "url_encode", + "input": data, + "output": encoded, + } + + def _url_decode(self, data: str) -> dict: + if not data: + return {"success": False, "error": "INPUT required"} + + decoded = unquote(data) + console.print(f"[green]decoded:[/green] {decoded}") + return { + "success": True, + "operation": "url_decode", + "input": data, + "output": decoded, + } + + def show_operations(self): + """Display available operations""" + from rich.table import Table + + table = Table(title="Available Operations", show_header=True) + table.add_column("Operation", style="cyan", width=15) + table.add_column("Description", style="dim") + + ops = [ + ("base64_encode", "Encode to base64"), + ("base64_decode", "Decode from base64"), + ("url_encode", "URL encode string"), + ("url_decode", "URL decode string"), + ] + + for op, desc in ops: + table.add_row(op, desc) + + console.print(table) + console.print("\n[dim]Usage: [cyan]set OPERATION [/cyan][/dim]") + + def info(self) -> dict: + return { + "name": self.NAME, + "description": self.DESCRIPTION, + "author": self.AUTHOR, + "category": self.CATEGORY, + "operations": { + "base64_encode": "base64 encode", + "base64_decode": "base64 decode", + "url_encode": "url encode", + "url_decode": "url decode", + }, + "examples": [ + "set INPUT 'Hello World' && set OPERATION base64_encode && run", + "set INPUT 'SGVsbG8gV29ybGQ=' && set OPERATION base64_decode && run", + "set INPUT 'hello world!' && set OPERATION url_encode && run", + "set INPUT 'hello%20world%21' && set OPERATION url_decode && run", + ], + } diff --git a/src/hatiyar/modules/misc/misc.yaml b/src/hatiyar/modules/misc/misc.yaml index 2a4da62..50e9a23 100644 --- a/src/hatiyar/modules/misc/misc.yaml +++ b/src/hatiyar/modules/misc/misc.yaml @@ -3,7 +3,12 @@ modules: - id: hello_world name: "Hello World" + module_path: "misc.hello_world" + category: "misc" description: "Minimal test module that prints a message" - version: "1.0" + + - id: encoders + name: "Encoders" + module_path: "misc.encoders" category: "misc" - module_path: "misc.hello_world" + description: "Base64 and URL encoding/decoding utilities" diff --git a/uv.lock b/uv.lock index 61ccaa1..94fbeb9 100644 --- a/uv.lock +++ b/uv.lock @@ -602,7 +602,7 @@ wheels = [ [[package]] name = "hatiyar" -version = "0.0.3" +version = "0.3.0" source = { editable = "." } dependencies = [ { name = "boto3", version = "1.7.84", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },