Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/hatiyar/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,18 +479,20 @@ def handle_show(args: List[str], console, session: CLISession) -> None:
if not args:
console.print("[red]Usage: show <what>[/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]"
)


Expand Down Expand Up @@ -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]")
Expand Down
2 changes: 1 addition & 1 deletion src/hatiyar/core/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
22 changes: 17 additions & 5 deletions src/hatiyar/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
140 changes: 140 additions & 0 deletions src/hatiyar/modules/misc/encoders.py
Original file line number Diff line number Diff line change
@@ -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 <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",
],
}
9 changes: 7 additions & 2 deletions src/hatiyar/modules/misc/misc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.