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
2 changes: 1 addition & 1 deletion devha/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.0"
__version__ = "2.0.0"
95 changes: 87 additions & 8 deletions devha/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Main Typer application for devha."""
"""Main Typer application for devha โ€” Hacking Studio v2.0."""

from __future__ import annotations

import subprocess
import sys
from typing import Annotated

import typer
Expand All @@ -20,18 +22,27 @@
harvest,
headers,
ping,
wifilab,
passlab,
packetlab,
)
from devha.commands.devscanner import devscanner

app = typer.Typer(
name="devha",
help="[cyan]Developer & Hacking CLI[/cyan] โ€” ethical hacking and developer toolkit.",
help="[cyan]devha Hacking Studio v2.0[/cyan] โ€” ethical hacking & developer toolkit.",
rich_markup_mode="rich",
no_args_is_help=True,
no_args_is_help=False,
add_completion=True,
)

# Register sub-apps
app.add_typer(cipher.app, name="cipher", help="Classical cipher encode / decode / crack.")
# โ”€โ”€โ”€ Sub-apps (grouped commands) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
app.add_typer(cipher.app, name="cipher", help="๐Ÿ” Classical & modern ciphers.")
app.add_typer(wifilab.app, name="wifilab", help="๐Ÿ“ก WiFi Lab โ€” scan, map devices, test own AP.")
app.add_typer(passlab.app, name="passlab", help="๐Ÿ”‘ Password Lab โ€” hash, crack, generate.")
app.add_typer(packetlab.app, name="packetlab", help="๐Ÿ“ฆ Packet Lab โ€” capture, ARP scan, builder.")

# โ”€โ”€โ”€ Single commands โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
app.command("portscan")(portscan.portscan)
app.command("username")(username.username)
app.command("wifi")(wifi.wifi)
Expand All @@ -41,11 +52,49 @@
app.command("harvest")(harvest.harvest)
app.command("headers")(headers.headers)
app.command("ping")(ping.ping)
app.command("devscanner")(devscanner)


# โ”€โ”€โ”€ Studio TUI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

@app.command("studio")
def studio(
no_fx: Annotated[bool, typer.Option("--no-fx", help="Skip boot animation.")] = False,
) -> None:
"""
๐ŸŽฎ Open the interactive Hacking Studio TUI menu.

Navigate with [1-9] keys. All tools available in one interface.
"""
if not no_fx:
from devha.fx import hacker_boot
hacker_boot()

from devha.studio import run_studio
module = run_studio()

# Map module name โ†’ CLI command to run
_module_map = {
"network": ["devha", "devscanner", "--help"],
"wifi": ["devha", "wifilab", "--help"],
"osint": ["devha", "username", "--help"],
"cipher": ["devha", "cipher", "--help"],
"web": ["devha", "dirscan", "--help"],
"password": ["devha", "passlab", "--help"],
"packets": ["devha", "packetlab", "--help"],
"headers": ["devha", "headers", "--help"],
"ping": ["devha", "ping", "--help"],
}

if module and module in _module_map:
subprocess.run(_module_map[module])


# โ”€โ”€โ”€ Version + main callback โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def _version_callback(value: bool) -> None:
if value:
console.print(f"[cyan]devha[/cyan] version [bold]{__version__}[/bold]")
console.print(f"[cyan]devha[/cyan] Hacking Studio version [bold]{__version__}[/bold]")
raise typer.Exit()


Expand All @@ -57,7 +106,37 @@ def main(
typer.Option("--version", "-V", callback=_version_callback, is_eager=True, help="Show version."),
] = False,
no_banner: Annotated[bool, typer.Option("--no-banner", help="Skip the ASCII banner.")] = False,
studio_mode: Annotated[bool, typer.Option("--studio", "-s", help="Launch interactive Studio TUI.")] = False,
) -> None:
"""[cyan bold]devha[/cyan bold] โ€” Developer & Hacking CLI."""
if not no_banner and ctx.invoked_subcommand is not None:
"""[cyan bold]devha[/cyan bold] Hacking Studio โ€” Developer & Hacking CLI v2.0."""
if studio_mode:
ctx.invoke(studio)
return

if ctx.invoked_subcommand is None:
# No subcommand โ†’ show studio TUI
from devha.fx import hacker_boot
if not no_banner:
hacker_boot()
from devha.studio import run_studio
module = run_studio()
if module:
_launch_module(module)
elif not no_banner:
print_banner()


def _launch_module(module: str) -> None:
_map = {
"network": ["devha", "devscanner", "--help"],
"wifi": ["devha", "wifilab", "--help"],
"osint": ["devha", "username", "--help"],
"cipher": ["devha", "cipher", "--help"],
"web": ["devha", "dirscan", "--help"],
"password": ["devha", "passlab", "--help"],
"packets": ["devha", "packetlab", "--help"],
"headers": ["devha", "headers", "--help"],
"ping": ["devha", "ping", "--help"],
}
if module in _map:
subprocess.run(_map[module])
104 changes: 100 additions & 4 deletions devha/commands/cipher.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""Cipher command: encode, decode, crack classical ciphers."""
"""Cipher command: encode, decode, crack classical ciphers + modern encodings."""

from __future__ import annotations

import base64
import json
from typing import Annotated

import typer
from rich.text import Text

from devha.ui import console, make_table, print_panel, error
from devha.ui import console, make_table, print_panel, error, info

app = typer.Typer(help="Classical cipher [encode / decode / crack].", rich_markup_mode="rich")

Expand Down Expand Up @@ -94,7 +95,6 @@ def vigenere_decode(text: str, key: str) -> str:


def _readability_score(text: str) -> float:
"""Score 0-100 based on English letter frequency match."""
letters = [ch.lower() for ch in text if ch.isalpha()]
if not letters:
return 0.0
Expand All @@ -103,6 +103,67 @@ def _readability_score(text: str) -> float:
return round(score, 2)


# โ”€โ”€โ”€ Modern encodings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def base64_encode(text: str) -> str:
return base64.b64encode(text.encode()).decode()

def base64_decode(text: str) -> str:
return base64.b64decode(text.encode()).decode(errors="replace")

def hex_encode(text: str) -> str:
return text.encode().hex()

def hex_decode(text: str) -> str:
try:
return bytes.fromhex(text).decode(errors="replace")
except ValueError:
return "Invalid hex string"

def rot47_encode(text: str) -> str:
result = []
for ch in text:
n = ord(ch)
if 33 <= n <= 126:
result.append(chr(33 + (n - 33 + 47) % 94))
else:
result.append(ch)
return "".join(result)

def binary_encode(text: str) -> str:
return " ".join(format(ord(c), "08b") for c in text)

def binary_decode(text: str) -> str:
try:
return "".join(chr(int(b, 2)) for b in text.split())
except ValueError:
return "Invalid binary string"

def morse_encode(text: str) -> str:
_MORSE = {
"A": ".-", "B": "-...", "C": "-.-.", "D": "-..", "E": ".", "F": "..-.",
"G": "--.", "H": "....", "I": "..", "J": ".---", "K": "-.-", "L": ".-..",
"M": "--", "N": "-.", "O": "---", "P": ".--.", "Q": "--.-", "R": ".-.",
"S": "...", "T": "-", "U": "..-", "V": "...-", "W": ".--", "X": "-..-",
"Y": "-.--", "Z": "--..", "0": "-----", "1": ".----", "2": "..---",
"3": "...--", "4": "....-", "5": ".....", "6": "-....", "7": "--...",
"8": "---..", "9": "----.", " ": "/",
}
return " ".join(_MORSE.get(c.upper(), "?") for c in text)

def morse_decode(text: str) -> str:
_MORSE_REV = {
".-": "A", "-...": "B", "-.-.": "C", "-..": "D", ".": "E", "..-.": "F",
"--.": "G", "....": "H", "..": "I", ".---": "J", "-.-": "K", ".-..": "L",
"--": "M", "-.": "N", "---": "O", ".--.": "P", "--.-": "Q", ".-.": "R",
"...": "S", "-": "T", "..-": "U", "...-": "V", ".--": "W", "-..-": "X",
"-.--": "Y", "--..": "Z", "-----": "0", ".----": "1", "..---": "2",
"...--": "3", "....-": "4", ".....": "5", "-....": "6", "--...": "7",
"---..": "8", "----.": "9", "/": " ",
}
return "".join(_MORSE_REV.get(c, "?") for c in text.split())


# โ”€โ”€โ”€ Typer sub-commands โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€


Expand Down Expand Up @@ -194,16 +255,51 @@ def _apply_cipher(text: str, cipher_type: str, key: str, mode: str) -> str:
return caesar_encode(text, shift) if mode == "encode" else caesar_decode(text, shift)
elif ct == "rot13":
return rot13_encode(text)
elif ct == "rot47":
return rot47_encode(text)
elif ct == "atbash":
return atbash_encode(text)
elif ct == "vigenere":
if not key.isalpha():
error("Vigenere key must be alphabetic.")
raise typer.Exit(1)
return vigenere_encode(text, key) if mode == "encode" else vigenere_decode(text, key)
elif ct == "base64":
return base64_encode(text) if mode == "encode" else base64_decode(text)
elif ct == "hex":
return hex_encode(text) if mode == "encode" else hex_decode(text)
elif ct == "binary":
return binary_encode(text) if mode == "encode" else binary_decode(text)
elif ct == "morse":
return morse_encode(text) if mode == "encode" else morse_decode(text)
else:
error(f"Unknown cipher: {cipher_type}. Choose: caesar, vigenere, rot13, atbash")
error(f"Unknown cipher: {cipher_type}. Choose: caesar, vigenere, rot13, rot47, atbash, base64, hex, binary, morse")
raise typer.Exit(1)
except ValueError:
error(f"Invalid key '{key}' for {cipher_type}.")
raise typer.Exit(1)


@app.command("all")
def encode_all(
text: Annotated[str, typer.Argument(help="Text to encode in all formats.")],
json_out: Annotated[bool, typer.Option("--json")] = False,
) -> None:
"""Encode text in ALL available formats at once."""
results = {
"ROT13": rot13_encode(text),
"ROT47": rot47_encode(text),
"Caesar+13": caesar_encode(text, 13),
"Atbash": atbash_encode(text),
"Base64": base64_encode(text),
"Hex": hex_encode(text),
"Binary": binary_encode(text),
"Morse": morse_encode(text),
}
if json_out:
console.print_json(json.dumps({"input": text, "encodings": results}))
return
table = make_table("FORMAT", "OUTPUT", title=f"๐Ÿ” All encodings of '{text[:30]}'")
for fmt, out in results.items():
table.add_row(f"[cyan]{fmt}[/cyan]", out[:80])
console.print(table)
Loading
Loading