hackingtool/hackingtool.py
Hardik Zinzuvadiya 01a51bbca6
Restructure for v2.0.0 with new tools, features, and UI updates (#590)
* Phase 0: Add foundation files for v2.0.0 restructure

- constants.py: single source of truth for repo URLs, version (2.0.0),
  all paths via Path.home(), UI theme constants, PRIV_CMD auto-detection
- os_detect.py: OSInfo dataclass, auto-detect OS/distro/package manager,
  CURRENT_OS singleton, per-OS install command maps
- config.py: get_tools_dir(), load()/save() config.json, get_sudo_cmd()
- tools/__init__.py, tools/others/__init__.py: make proper Python packages
- IMPLEMENTATION.md: full 18-section restructuring plan (2350+ lines)
- LOG.md: 13-phase progress tracker

* Phase 1+2: Fix all 30 critical bugs and 4 security vulnerabilities

* Phase 3: Installation & path overhaul

* Phase 4+5: Core architecture + shared console across all tool files

Phase 4 (core.py — largely done in Phase 1, completed here):
- HackingTool: add ARCHIVED, ARCHIVED_REASON, SUPPORTED_OS, REQUIRES_* fields
- HackingTool: remove INSTALLATION_DIR (unused)
- HackingToolsCollection: add _active_tools(), _archived_tools(), _incompatible_tools()
- HackingToolsCollection: add _show_archived_tools() (option 98 sub-menu)
- HackingToolsCollection.show_options(): filter by OS and ARCHIVED flag
- OS-incompatible tools show count but are hidden from menu
- Archived tools accessible via option 98 with reason displayed

Phase 5 (all 22 remaining tool files):
- Remove local console = Console() and _theme = Theme() from all 22 files
- Remove P_COLOR and PURPLE_STYLE local constants
- Add `from core import HackingTool, HackingToolsCollection, console` everywhere
- Remove show_options() overrides from all collection classes (500+ lines deleted)
- Remove pretty_print() overrides from all collection classes
- Remove _get_attr() / _get_attr_fallback() helpers from all collection classes
- Replace super(ClassName, self).__init__() → super().__init__() in all files
- Remove # coding=utf-8 headers from all files
- Fix remaining PURPLE_STYLE usages → "bold magenta" literal

All 28 tool modules import cleanly. Zero local console instances remain.

* Phase 6: Naming & structure cleanup

- Rename tools/phising_attack.py → phishing_attack.py (fix typo)
- Rename tools/information_gathering_tools.py → information_gathering.py
- Rename tools/wireless_attack_tools.py → wireless_attack.py
- Rename tools/forensic_tools.py → forensics.py
- Rename tools/sql_tools.py → sql_injection.py
- Rename tools/webattack.py → web_attack.py
- Rename class autophisher → Autophisher (PEP-8 CapWords)
- Rename class ddos → DDoSTool (PEP-8 CapWords), TITLE "ddos" → "DDoS"
- Update all imports in hackingtool.py and exploit_frameworks.py

* Phase 7: Tool install command fixes

- Replace setup.py install → pip install --user . (explo, takeover, reconspider, infoga)
- Mark Blazy as ARCHIVED: Python 2 only (pip2.7/python2.7)
- Replace sudo git clone → git clone everywhere (no root needed for user tools dir)
- Replace sudo pip install → pip install --user everywhere
- Fix dalfox: git clone + cd approach → go install github.com/hahwul/dalfox/v2@latest
- Add apt -y flag to ReconSpider apt install

* Phase 8: Mark archived tools and add SUPPORTED_OS flags

- Remove stale sys.path.append hack from forensics.py (unused since package structure)
- Add SUPPORTED_OS = ["linux"] to BulkExtractor, Guymager (apt/Linux-only)
- Add SUPPORTED_OS = ["linux"] to all android_attack.py tools (bash scripts)
- Add SUPPORTED_OS = ["linux"], REQUIRES_WIFI = True to wifi_jamming.py tools
- Fix WifiJammerNG RUN_COMMANDS: python → python3, remove boxes/lolcat pipe

* Phase 11: requirements.txt and Dockerfile cleanup

- requirements.txt: remove boxes, lolcat (system CLI tools not pip packages),
  flask (unused), requests (unused at runtime); pin rich>=13.0.0
- Dockerfile: add --break-system-packages to pip3 install (PEP 668 compliance
  on Kali/Debian externally-managed Python environments)

* Phase 13: Python 3 modernization and os.system cleanup

- Replace os.system("cd X; ...") no-op cd subshells with subprocess.run(cwd=...)
  in: xss_attack.py (XSSCon, XanXSS), payload_creator.py (TheFatRat update/troubleshoot),
      forensics.py (BulkExtractor gui/cli), phishing_attack.py (BlackPhish update)
- Replace os.system echo+boxes+lolcat in post_exploitation.py with console.print
- Fix socialmedia_finder.py: print()+os.system+lolcat → subprocess+console.print
- Fix forensics.py cli_mode: os.system apt/bulk_extractor → subprocess.run list form

* Phase 13 (cont): Final os.system cleanup

- anonsurf.py: os.system("sudo anonsurf stop") → subprocess.run list form
- tool_manager.py: os.system(f"{priv}{cmd}") → subprocess.run(shell=True)
  (shell=True justified: cmd is from hardcoded PACKAGE_UPDATE_CMDS dict, not user input)

* Phase 9: Add SUPPORTED_OS flags to remaining Linux-only tools

Mark bash-script and apt-dependent tools as linux-only so they are
hidden automatically on macOS via _active_tools() filter in core.py:
- phishing_attack.py: 14 tools (all bash-script based)
- ddos.py: all 6 tools (Linux network stack required)
- post_exploitation.py: Vegile, ChromeKeyLogger
- payload_creator.py: all 7 tools (bash setup scripts)
- exploit_frameworks.py: WebSploit (bash install)
- reverse_engineering.py: Apk2Gold (bash build)
- xss_attack.py: RVuln (Rust + apt deps)

* Phase 10: Add modern tools across 6 categories

Information Gathering (+7):
  theHarvester, Amass, Masscan, RustScan, Holehe, Maigret, httpx

Web Attack (+6):
  Nuclei, ffuf, Feroxbuster, Nikto, wafw00f, Katana

Wordlist/Password (+3):
  Hashcat, John the Ripper, haiti

Wireless Attack (+3):
  Airgeddon, hcxdumptool, hcxtools

Forensics (+2):
  Volatility3, Binwalk

Post Exploitation (+1):
  pwncat-cs

* Phase 12: Update README for v2.0.0

- Version badge: v1.2.0 -> v2.0.0
- Python badge: Python-3 -> Python-3.10+
- Add macOS to platform badge
- Add 'What's new in v2.0.0' section replacing stale v1.2.0 notes
- Regenerate full tool list — 22 new tools marked with new
- Fix docker build typo: 'docker buitl' -> 'docker build'
- Add Requirements section (Python 3.10+, Go 1.21+, Ruby)
- Remove stale 'To do' checklist and old Docker output block

* Redesign README, templates, and update .github workflows

README.md:
- Modern centered header with full badge row
- Stats banner: 17 categories, 150+ tools
- What's new as a comparison table
- Tool category table with per-category counts
- Complete tool listing with star markers for new tools
- Contributing section with enforced title formats for issues and PRs
- Star History chart (Z4nzu/hackingtool)
- Cleaned up Installation, Requirements, Social sections

README_template.md:
- Full redesign matching README.md layout
- {{toc}} and {{tools}} placeholders preserved for generate_readme.py
- Contributing section with title format rules embedded in template

.github/ISSUE_TEMPLATE/bug_report.md:
- Remove irrelevant browser/smartphone sections
- Add tool name, category, error output, environment table

.github/ISSUE_TEMPLATE/feature_request.md:
- Tighten to 4 focused fields, note tool additions use separate template

.github/ISSUE_TEMPLATE/tool_request.md (new):
- Structured template for new tool suggestions
- Required fields: name, URL, category, OS, install/run commands, reason
- Enforced title format: [Tool Request] ToolName - Category

.github/PULL_REQUEST_TEMPLATE.md (new):
- PR checklist for new tool additions and other change types
- Enforced title format: [New Tool] / [Fix] / [Improve]
- All required class fields listed as checklist items

.github/workflows/lint_python.yml:
- actions/checkout v3 -> v4, setup-python v4 -> v5
- python-version: 3.x -> 3.12 (project requires 3.10+)
- ruff target-version: py37 -> py310
- ruff --show-source -> --output-format=full (updated flag name)
- Remove deprecated safety check command

.github/workflows/test_install.yml:
- actions/checkout v3 -> v4, setup-python v4 -> v5
- python-version: 3.x -> 3.12
- Replace fragile matrix of hard-coded menu numbers with
  two simple smoke tests: launch+quit, and navigate+back

* Improve Dockerfile, docker-compose, add .dockerignore

Dockerfile:
- Add '# syntax=docker/dockerfile:1' to enable BuildKit features
- Add LABEL metadata (OCI image spec)
- Remove unused apt packages: sudo, python3-venv
- Replace --no-cache-dir with --mount=type=cache for pip (faster rebuilds)
- Add comments explaining each decision

docker-compose.yml:
- Remove deprecated 'version:' field (Compose v2 ignores it, shows warning)
- Add 'image: hackingtool:latest' tag for clarity
- Add 'restart: unless-stopped' for production service
- Add 'hackingtool-dev' profile service with live source volume mount
  so dev workflow (edit without rebuild) is separate from default run
- Clarify volume purpose in comments

.dockerignore (new):
- Exclude .git/, images/, __pycache__/, .github/, *.md, tests/
- Prevents multi-hundred-MB build context; dramatically reduces image size
- Keeps layer cache more stable (README changes no longer bust COPY layer)

README.md / README_template.md:
- Replace single-line Docker snippet with 3-option step-by-step guide:
  Option A: docker run -it --rm (no Compose)
  Option B: docker compose up -d + exec (recommended)
  Option C: docker compose --profile dev (live source mount)
- Add docker compose down / down -v stop instructions

* Fix Docker sudo error, 99 back navigation, and empty input exit

install.py:
- sudo prefix now determined by euid (== 0 means root) not by OS name
- Previously used 'sudo ' for all Linux — breaks inside Docker where we
  run as root but sudo is not installed
- Single priv variable computed once at top of install_system_packages()

Dockerfile:
- Add python3-venv back to apt installs
- Was removed as 'unused' but install.py uses 'python3 -m venv' to create
  the virtualenv in APP_INSTALL_DIR; missing package caused venv failure

core.py (HackingTool.show_options + HackingToolsCollection.show_options):
- 99 always returns now instead of sys.exit() when parent is None
  Previously: sub-menus with no parent called sys.exit() — since
  interact_menu() never passes parent, pressing 99 in any category
  exited the entire program instead of returning to the main menu
- Empty Enter (blank input) now continues the loop instead of
  defaulting to "99" and triggering exit
  Previously: Prompt default="99" meant pressing Enter without typing
  anything was treated as choosing exit
- "Exit" label on 99 row changed to "Main Menu" since it now returns

* Restore Description column and 1-based numbering in collection menus

HackingToolsCollection.show_options():
- Add Description column back (was dropped during restructure)
- Show first line of description; '—' for tools with no description
- Switch to box.SIMPLE_HEAD with show_lines=True for row separators
- Numbering changed from 0-based to 1-based (index 1..N)
- Input validation updated: 1 <= choice <= len(active) with active[choice-1]

* Redesign main menu: compact header, 2-column grid, 1-based numbering, ? help

Header:
- Replace 8-line pixelated ASCII logo with a compact 9-line styled panel
- Shows tool name, version, repo URL, and legal warning in one block
- Screen is now cleared inside build_menu() on each main menu render

Menu layout:
- Single narrow column → 2-column grid (9 left / 8 right)
- Full terminal width is used; short display labels prevent truncation
- tool_definitions gains a third field (menu_label) for the grid;
  full TITLE is still used when entering a category

Numbering:
- 0-based → 1-based throughout interact_menu()
- ToolManager is item 18 (was mislabeled 99, conflicting with exit)
- Input validation: 1 <= choice <= len(all_tools) → all_tools[choice - 1]

Commands:
- Add ? / help   → show Quick Help overlay (main/category/tool reference)
- Add q / quit / exit → clean exit with goodbye message
- Remove Confirm.ask("Return to main menu?") — pressing 99 in any
  sub-menu already returns here; the extra prompt was redundant friction
- Ctrl+C still works as force quit

Error messages updated to reflect new command set

* Redesign header: hacker split-layout with ASCII art + live system info

Replace compact text panel with a Metasploit-style split header:
- Left: "HT" block-letter ASCII art in bright green
- Vertical │ separator that runs the full height of the art block
- Right: live system stats — os, kernel, user@host, ip, tool count, session time
- Bottom: random hacker quote (10 options, changes on each launch)
- Bottom: authorized-use warning in dim red
- Panel title: '[ HackingTool v2.0.0 ]' left-aligned (terminal bracket style)
- Panel subtitle: '[ timestamp ]' right-aligned
- Border: bright_magenta HEAVY box

Live info gathered via platform, socket, os.getlogin() with fallbacks:
- OS: platform.freedesktop_os_release() → PRETTY_NAME, else platform.system()
- IP: UDP connect trick (no data sent, gets local outbound IP)
- User: os.getlogin() → USER env → LOGNAME env → 'root'

* Redesign header: full HACKING TOOL art + ? and q in all sub-menus

hackingtool.py:
- Replace 6-line "HT" art with full 12-line "HACKING TOOL" block letters
- Right side gains 3 more info lines: python version, arch, status
- 12 art lines paired with 12 stat lines for consistent separator
- Layout optimized for wide terminals (100+ chars); gracefully
  truncates on narrow ones

core.py — ? and q support in all sub-menus:
- Add _show_inline_help() function: compact navigation reference
  (1-N select, 99 back, 98 project page, ? help, q quit)
- HackingTool.show_options(): parse ? → inline help, q → SystemExit
- HackingToolsCollection.show_options(): same ? and q handling
- Both menus now show hint bar: "Enter number · ? help · q quit"
- Prompt changed from "[?] Select" to ">" for consistency with main menu
- q/quit/exit raises SystemExit(0) to cleanly exit from any depth

* Add install.sh one-liner and fix install.py to detect local source

install.sh (new):
- Standalone bash installer for curl | sudo bash one-liner
- Detects package manager (apt-get, pacman, dnf, brew)
- Installs prerequisites: git, python3, pip, venv
- Checks Python >= 3.10
- Clones repo with --depth 1 (shallow, faster)
- Creates venv + installs requirements
- Creates /usr/bin/hackingtool launcher
- Creates ~/.hackingtool/ dirs with correct ownership (SUDO_USER)

install.py:
- Add _is_source_dir() — detects if install.py is being run from a local
  clone (hackingtool.py exists alongside it)
- install_source() replaces git_clone() — copies source to /usr/share/
  instead of re-cloning when running from a local clone
- Falls back to git clone --depth 1 when not in a source directory
- Eliminates the redundant clone-after-clone pattern

README.md / README_template.md:
- Add one-liner install as primary method
- Keep manual git clone + install.py as alternative
- Remove sudo from hackingtool run command (launcher handles it)

* Add 35 new tools across 3 new + 6 existing categories

New categories:
- tools/active_directory.py: BloodHound, NetExec (nxc), Impacket,
  Responder, Certipy, Kerbrute (6 tools)
- tools/cloud_security.py: Prowler, ScoutSuite, Pacu, Trivy (4 tools)
- tools/mobile_security.py: MobSF, Frida, Objection (3 tools)

Existing categories expanded:
- information_gathering.py: +SpiderFoot, Subfinder, TruffleHog, Gitleaks (4)
- web_attack.py: +Gobuster, Dirsearch, OWASP ZAP, testssl.sh, Arjun,
  Caido, mitmproxy (7)
- post_exploitation.py: +Sliver, Havoc, PEASS-ng, Ligolo-ng, Chisel,
  Evil-WinRM, Mythic (7)
- reverse_engineering.py: +Ghidra, Radare2 (2)
- forensics.py: +pspy (1)
- wireless_attack.py: +Bettercap (1)

hackingtool.py:
- Import 3 new category modules
- Add 3 new entries to tool_definitions (AD, Cloud, Mobile)
- Add 3 new instances to all_tools list
- Categories: 17 -> 20, total tools: 150+ -> 185+
- Help overlay updated for new range (1-20, 21=Update)

* 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)

* Feature: Show installed status (✔/✘) next to each tool

core.py:
- Add is_installed property to HackingTool class
  Checks: (1) shutil.which() for binary from first RUN_COMMAND,
  (2) os.path.isdir() for git clone target directory
  Handles "cd foo && binary" and "sudo binary" patterns
- Add status column to HackingToolsCollection.show_options() table
  ✔ (green) = installed, ✘ (dim) = not installed
- Archived/back rows updated for extra column

* Feature: Tag-based filtering with auto-derived tags (t command)

core.py:
- Add TAGS field to HackingTool class (list[str], default empty)
- Allows manual tag override per tool

hackingtool.py:
- Add _get_all_tags() — builds tag index from 19 regex rules that
  auto-derive tags from tool TITLE + DESCRIPTION (osint, scanner, c2,
  web, cloud, mobile, wireless, forensics, reversing, etc.)
- Manual TAGS on a tool class take priority over auto-derived
- Add filter_by_tag() — shows all available tags with tool counts,
  user picks a tag, results shown with installed status, select to
  jump directly into tool.show_options()
- Wire t/tag/tags/filter commands into interact_menu()
- Search also matches against TAGS field
- Updated hint bar: / search · t tags · ? help · q quit
- Updated help overlay with tag filter entry

* Feature: Install all tools in a category (option 97)

HackingToolsCollection.show_options():
- Count not-installed tools using is_installed property
- Show option 97 'Install all (N not installed)' when N > 0
- On select: iterates all not-installed tools, calls tool.install()
  with progress counter (1/N, 2/N, ...) and error handling per tool
- Hidden when all tools are already installed
- Updated inline help to mention option 97

* Feature: Update command for each tool (option 3 in tool menu)

HackingTool:
- Add update() method — detects install method from INSTALL_COMMANDS
  and runs the appropriate update:
  git clone → git -C <dir> pull
  pip install → pip install --upgrade
  go install → re-runs go install (fetches latest)
  gem install → gem update
- Checks is_installed first; shows warning if not installed
- Added as option 3 in every tool's OPTIONS menu (Install, Run, Update)

* Feature: Tool recommendations — "I want to do X" (r command)

- Add _RECOMMENDATIONS dict — maps 22 common tasks to tag names:
  "scan a network" → scanner, "crack passwords" → bruteforce/credentials,
  "pentest active directory" → active-directory, "pivot through network"
  → network, etc.
- Add recommend_tools() — shows numbered task list, user picks one,
  shows all matching tools with installed status, select to jump in
- Wire r/rec/recommend into interact_menu()
- Updated hint bar and help overlay

* Update README with all new features, 3 new categories, 35 new tools

- Stats banner: 17 → 20 categories, 150+ → 185+ tools
- What's New table: add 10 new feature entries (search, tags, recommend,
  install status, install all, update, one-liner, new categories/tools)
- Add Quick Commands reference table (/, t, r, ?, q, 97, 99)
- Tool Categories table: add Active Directory, Cloud Security,
  Mobile Security rows; update tool counts for expanded categories
- Add 3 new category sections: Active Directory Tools (6), Cloud
  Security Tools (4), Mobile Security Tools (3) with GitHub links
- Mark all 35 new tools with ★ in their respective category sections

* UI: Dual-line prompt and inline /query search

Prompt style (all menus):
- Replace bare '>' with '╰─>' connected to the hint bar above
- Hint bar uses Claude CLI style: first letter highlighted in cyan,
  rest dimmed (/search  tags  recommend  ?help  quit)
- Creates a visual dual-line input area similar to Claude Code CLI

Inline search:
- Typing /subdomain at the main prompt immediately searches and
  shows results — no second "enter query" prompt needed
- /  (bare slash) still opens the interactive search prompt
- s  still works as a search alias
- search_tools() now accepts optional query parameter

core.py sub-menus:
- Same dual-line prompt style: hint bar + ╰─>
- Compact hint: ?help  quit  99 back

* Fix is_installed crash on sub-collections (OtherTools)

OtherTools.TOOLS contains HackingToolsCollection instances (like
SocialMediaBruteforceTools) which don't have the is_installed property.
- Guard is_installed access with hasattr() in both the status column
  and the not_installed count for Install All
- Sub-collections show blank status; individual tools show ✔/✘

* Add Buy Me a Coffee link to README and FUNDING.yml

- README.md: Add Support section with Buy Me a Coffee button above Social
- .github/FUNDING.yml: Add buy_me_a_coffee: hardikzinzu
  (enables GitHub Sponsor button on the repo page)

* Feature: Open Folder option to manually access tool directory

HackingTool:
- Add _get_tool_dir() — finds the tool's local directory by checking:
  1. Git clone target dir (from INSTALL_COMMANDS)
  2. "cd foo && bar" pattern in RUN_COMMANDS
  3. Binary location via shutil.which() → dirname
- Add open_folder() — opens the tool's directory in a new shell ($SHELL)
  so the user can inspect files, run manual install steps, or debug
  If dir not found: shows a helpful message with git clone command
- Added as option 4 in every tool menu (Install, Run, Update, Open Folder)
- Updated inline help to mention the tool menu options

* Add SVG logo and update README header

images/logo.svg:
- Terminal-style dark background with rounded corners
- Red/yellow/green window dots (macOS style)
- Green shield with skull & crossbones icon
- "HACKING" in purple-to-pink gradient with glow
- "TOOL" in hacker green with glow effect
- Blinking cursor animation, binary numbers, corner brackets
- Monospace font, 600x200 viewport

README.md:
- Replace plain # HackingTool heading with centered logo SVG

* Redesign README with modern GitHub features

Header:
- for-the-badge style badges with logos and custom colors
- Stats counter table (20 categories, 185+ tools, 19 tags, 2 platforms)
- CTA buttons: Install Now, Quick Commands, Suggest a Tool

What's New:
- Wrapped in collapsible <details open> with icon-prefixed table rows
- Each feature has an emoji icon for visual scanning

Quick Commands:
- Centered table with Works-in column showing scope

Tool Categories:
- 2-column table layout showing all 20 categories side by side
- Wrapped in collapsible section

Tool lists:
- Every category wrapped in collapsible <details> sections
- Keeps README scannable — users expand only what they need
- New tools still marked with star

Contributing:
- Side-by-side table: Issue template left, PR template right

Installation:
- Side-by-side table: One-liner left, Manual right
- Docker in collapsible section (not everyone needs it)

Requirements:
- Table format with version, dependency, and "needed for" columns

Support/Social:
- Centered layout with for-the-badge Twitter/GitHub follow buttons
- Larger Buy Me a Coffee button

* Redesign README header, add category emojis, left-align support

Header:
- Consolidate badges into 2 clean rows instead of 4 sparse rows
- Row 1: flat-square badges (license, python, version, stars, forks,
  issues, last commit) — compact, inline, no awkward gaps
- Row 2: for-the-badge stat pills (20 Categories, 185+ Tools, 19 Tags,
  Linux|Kali|Parrot|macOS) — single row, removes duplicate Platform badge
- CTA buttons tightened with consistent spacing

Category headings:
- Add emoji to every ## category heading throughout the README
  (matching the index table emojis)

Support section:
- Left-aligned instead of centered
- Social split into its own heading

Also removes all <details>/<summary> collapse wrappers — all sections
are plain visible as requested

* Fix missing imports: subprocess in payload_creator, Table in wordlist_generator

- payload_creator.py: add 'import subprocess' (used by TheFatRat methods)
- wordlist_generator.py: add 'from rich.table import Table' (used by show_options)
- Fixes 6 ruff F821 'Undefined name' errors

* Fix codespell error: 'ags' flagged as misspelling in hint bar

Restructure the hint bar string so shortcut letters and their labels
are separate tokens ('t' + 'tags') instead of split across Rich markup
tags ('t' + 'ags') which codespell flagged as a misspelling.

* Fix 12 issues from Copilot PR review (#590)

post_exploitation.py:
- Rename INSTALL_OS -> SUPPORTED_OS in Havoc class (typo, field was ignored)
- Sliver: replace curl|sudo bash pipe with download-then-execute pattern

ddos.py:
- Add DDoSTool() to DDOSTools.TOOLS list (was defined but unreachable)

phishing_attack.py:
- Rename class Evilginx2 -> Evilginx3 (installs v3 via go install)
- Update instance in TOOLS list to match
- Fix stale comment: wireless_attack_tools.py -> wireless_attack.py

forensics.py:
- Remove installable=False from Guymager (conflicted with INSTALL_COMMANDS)

tool_manager.py:
- Skip sudo prefix when already root (os.geteuid() == 0), matching
  the pattern already used in install.py

install.py:
- Add chown -R root:root after cp -a to prevent git "dubious ownership"
  errors when the source clone has different ownership

update.sh:
- Add git config safe.directory before pull to prevent dubious ownership
- Add --upgrade flag to pip install so dependencies actually update

os_detect.py:
- Add pkg (FreeBSD) entries to PACKAGE_INSTALL_CMDS, PACKAGE_UPDATE_CMDS,
  and REQUIRED_PACKAGES — was detected but had no command mappings (KeyError)

Skipped (not applicable):
- #1 subprocess import: already fixed in prior commit
- #11 Path.home() under sudo: by design (installer runs as root)
2026-03-15 20:03:08 +05:30

687 lines
27 KiB
Python
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import sys
# ── Python version guard (must be before any other local import) ───────────────
if sys.version_info < (3, 10):
print(
f"[ERROR] Python 3.10 or newer is required.\n"
f"You are running Python {sys.version_info.major}.{sys.version_info.minor}.\n"
f"Upgrade with: sudo apt install python3.10"
)
sys.exit(1)
import os
import platform
import socket
import datetime
import random
import webbrowser
from itertools import zip_longest
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.prompt import Prompt, Confirm
from rich.align import Align
from rich.text import Text
from rich import box
from rich.rule import Rule
from rich.columns import Columns
from core import HackingToolsCollection, clear_screen, console
from constants import VERSION_DISPLAY, REPO_WEB_URL
from config import get_tools_dir
from tools.anonsurf import AnonSurfTools
from tools.ddos import DDOSTools
from tools.exploit_frameworks import ExploitFrameworkTools
from tools.forensics import ForensicTools
from tools.information_gathering import InformationGatheringTools
from tools.other_tools import OtherTools
from tools.payload_creator import PayloadCreatorTools
from tools.phishing_attack import PhishingAttackTools
from tools.post_exploitation import PostExploitationTools
from tools.remote_administration import RemoteAdministrationTools
from tools.reverse_engineering import ReverseEngineeringTools
from tools.sql_injection import SqlInjectionTools
from tools.steganography import SteganographyTools
from tools.tool_manager import ToolManager
from tools.web_attack import WebAttackTools
from tools.wireless_attack import WirelessAttackTools
from tools.wordlist_generator import WordlistGeneratorTools
from tools.xss_attack import XSSAttackTools
from tools.active_directory import ActiveDirectoryTools
from tools.cloud_security import CloudSecurityTools
from tools.mobile_security import MobileSecurityTools
# ── Tool registry ──────────────────────────────────────────────────────────────
# (full_title, icon, menu_label)
# menu_label is the concise name shown in the 2-column main menu grid.
# full_title is shown when entering the category.
tool_definitions = [
("Anonymously Hiding Tools", "🛡 ", "Anonymously Hiding"),
("Information gathering tools", "🔍", "Information Gathering"),
("Wordlist Generator", "📚", "Wordlist Generator"),
("Wireless attack tools", "📶", "Wireless Attack"),
("SQL Injection Tools", "🧩", "SQL Injection"),
("Phishing attack tools", "🎣", "Phishing Attack"),
("Web Attack tools", "🌐", "Web Attack"),
("Post exploitation tools", "🔧", "Post Exploitation"),
("Forensic tools", "🕵 ", "Forensics"),
("Payload creation tools", "📦", "Payload Creation"),
("Exploit framework", "🧰", "Exploit Framework"),
("Reverse engineering tools", "🔁", "Reverse Engineering"),
("DDOS Attack Tools", "", "DDOS Attack"),
("Remote Administrator Tools (RAT)", "🖥 ", "Remote Admin (RAT)"),
("XSS Attack Tools", "💥", "XSS Attack"),
("Steganography tools", "🖼 ", "Steganography"),
("Active Directory Tools", "🏢", "Active Directory"),
("Cloud Security Tools", "", "Cloud Security"),
("Mobile Security Tools", "📱", "Mobile Security"),
("Other tools", "", "Other Tools"),
("Update or Uninstall | Hackingtool", "", "Update / Uninstall"),
]
all_tools = [
AnonSurfTools(),
InformationGatheringTools(),
WordlistGeneratorTools(),
WirelessAttackTools(),
SqlInjectionTools(),
PhishingAttackTools(),
WebAttackTools(),
PostExploitationTools(),
ForensicTools(),
PayloadCreatorTools(),
ExploitFrameworkTools(),
ReverseEngineeringTools(),
DDOSTools(),
RemoteAdministrationTools(),
XSSAttackTools(),
SteganographyTools(),
ActiveDirectoryTools(),
CloudSecurityTools(),
MobileSecurityTools(),
OtherTools(),
ToolManager(),
]
# Used by generate_readme.py
class AllTools(HackingToolsCollection):
TITLE = "All tools"
TOOLS = all_tools
# ── Help overlay ───────────────────────────────────────────────────────────────
def show_help():
console.print(Panel(
Text.assemble(
(" Main menu\n", "bold white"),
(" ─────────────────────────────────────\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"),
(" t ", "bold cyan"), ("filter tools by tag (osint, web, c2, ...)\n", "white"),
(" r ", "bold cyan"), ("recommend tools for a task\n", "white"),
(" ? ", "bold cyan"), ("show this help\n", "white"),
(" q ", "bold cyan"), ("quit hackingtool\n\n", "white"),
(" Inside a category\n", "bold white"),
(" ─────────────────────────────────────\n", "dim"),
(" 1N ", "bold cyan"), ("select a tool\n", "white"),
(" 99 ", "bold cyan"), ("back to main menu\n", "white"),
(" 98 ", "bold cyan"), ("open project page (if available)\n\n", "white"),
(" Inside a tool\n", "bold white"),
(" ─────────────────────────────────────\n", "dim"),
(" 1 ", "bold cyan"), ("install tool\n", "white"),
(" 2 ", "bold cyan"), ("run tool\n", "white"),
(" 99 ", "bold cyan"), ("back to category\n", "white"),
),
title="[bold magenta] ? Quick Help [/bold magenta]",
border_style="magenta",
box=box.ROUNDED,
padding=(0, 2),
))
Prompt.ask("[dim]Press Enter to return[/dim]", default="")
# ── Header: ASCII art + live system info ──────────────────────────────────────
# Full "HACKING TOOL" block-letter art — 12 lines, split layout with stats
_BANNER_ART = [
" ██╗ ██╗ █████╗ ██████╗██╗ ██╗██╗███╗ ██╗ ██████╗ ",
" ██║ ██║██╔══██╗██╔════╝██║ ██╔╝██║████╗ ██║██╔════╝ ",
" ███████║███████║██║ █████╔╝ ██║██╔██╗ ██║██║ ███╗",
" ██╔══██║██╔══██║██║ ██╔═██╗ ██║██║╚██╗██║██║ ██║",
" ██║ ██║██║ ██║╚██████╗██║ ██╗██║██║ ╚████║╚██████╔╝",
" ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ",
" ████████╗ ██████╗ ██████╗ ██╗",
" ╚══██╔══╝██╔═══██╗██╔═══██╗██║",
" ██║ ██║ ██║██║ ██║██║",
" ██║ ██║ ██║██║ ██║██║",
" ██║ ╚██████╔╝╚██████╔╝███████╗",
" ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝",
]
_QUOTES = [
'"The quieter you become, the more you can hear."',
'"Offense informs defense."',
'"There is no patch for human stupidity."',
'"In God we trust. All others we monitor."',
'"Hackers are the immune system of the internet."',
'"Every system is hackable — know yours before others do."',
'"Enumerate before you exploit."',
'"A scope defines your playground."',
'"The more you sweat in training, the less you bleed in battle."',
'"Security is a process, not a product."',
]
def _sys_info() -> dict:
"""Collect live system info for the header panel."""
info: dict = {}
# OS pretty name
try:
info["os"] = platform.freedesktop_os_release().get("PRETTY_NAME", "")
except Exception:
info["os"] = ""
if not info["os"]:
info["os"] = f"{platform.system()} {platform.release()}"
info["kernel"] = platform.release()
# Current user
try:
info["user"] = os.getlogin()
except Exception:
info["user"] = os.environ.get("USER", os.environ.get("LOGNAME", "root"))
info["host"] = socket.gethostname()
# Local IP — connect to a routable address without sending data
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0)
s.connect(("10.254.254.254", 1))
info["ip"] = s.getsockname()[0]
s.close()
except Exception:
info["ip"] = "127.0.0.1"
info["time"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
return info
def _build_header() -> Panel:
info = _sys_info()
# 12 stat lines paired with the 12 art lines
stat_lines = [
(" os ", info["os"][:34]),
(" kernel ", info["kernel"][:34]),
(" user ", f"{info['user']} @ {info['host'][:20]}"),
(" ip ", info["ip"]),
(" tools ", f"{len(all_tools)} categories · 185+ modules"),
(" session ", info["time"]),
("", ""),
(" python ", f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"),
(" arch ", platform.machine()),
(" status ", "✔ READY"),
("", ""),
("", ""),
]
grid = Table.grid(padding=0)
grid.add_column("art", no_wrap=True)
grid.add_column("sep", no_wrap=True)
grid.add_column("lbl", no_wrap=True)
grid.add_column("val", no_wrap=True)
for art_line, (lbl_text, val_text) in zip(_BANNER_ART, stat_lines):
grid.add_row(
Text(art_line, style="bold bright_green"),
Text("", style="dim green"),
Text(lbl_text, style="dim green"),
Text(val_text, style="bright_green"),
)
# Quote + warning below the split row
quote = random.choice(_QUOTES)
body = Table.grid(padding=(0, 0))
body.add_column()
body.add_row(grid)
body.add_row(Text(""))
body.add_row(Text(f" {quote}", style="italic dim"))
body.add_row(Text(" ⚠ For authorized security testing only",
style="bold dim red"))
return Panel(
body,
title=f"[bold bright_magenta][ HackingTool {VERSION_DISPLAY} ][/bold bright_magenta]",
title_align="left",
subtitle=f"[dim][ {info['time']} ][/dim]",
subtitle_align="right",
border_style="bright_magenta",
box=box.HEAVY,
padding=(0, 1),
)
# ── Main menu renderer ─────────────────────────────────────────────────────────
def build_menu():
clear_screen()
console.print(_build_header())
# ── 2-column category grid ──
# Items 1-17 in two columns, item 18 (ToolManager) shown separately
categories = tool_definitions[:-1] # 17 items
update_def = tool_definitions[-1] # ToolManager
mid = (len(categories) + 1) // 2 # 9 (left), 8 (right)
left = list(enumerate(categories[:mid], start=1))
right = list(enumerate(categories[mid:], start=mid + 1))
grid = Table.grid(padding=(0, 1), expand=True)
grid.add_column("ln", justify="right", style="bold magenta", width=5)
grid.add_column("li", width=3)
grid.add_column("lt", style="magenta", ratio=1, no_wrap=True)
grid.add_column("gap", width=3)
grid.add_column("rn", justify="right", style="bold magenta", width=5)
grid.add_column("ri", width=3)
grid.add_column("rt", style="magenta", ratio=1, no_wrap=True)
for (li, (_, lic, ll)), r in zip_longest(left, right, fillvalue=None):
if r:
ri, (_, ric, rl) = r
grid.add_row(str(li), lic, ll, "", str(ri), ric, rl)
else:
grid.add_row(str(li), lic, ll, "", "", "", "")
console.print(Panel(
grid,
title="[bold magenta] Select a Category [/bold magenta]",
border_style="bright_magenta",
box=box.ROUNDED,
padding=(0, 1),
))
# ── ToolManager row ──
tm_num = len(categories) + 1
console.print(
f" [bold magenta] {tm_num}[/bold magenta] {update_def[1]} "
f"[magenta]{update_def[2]}[/magenta]"
)
# ── Claude-style dual-line prompt area ──
console.print(Rule(style="dim magenta"))
console.print(
" [dim cyan]/[/dim cyan][dim]search[/dim] "
"[dim cyan]t[/dim cyan] [dim]tags[/dim] "
"[dim cyan]r[/dim cyan] [dim]recommend[/dim] "
"[dim cyan]?[/dim cyan] [dim]help[/dim] "
"[dim cyan]q[/dim cyan] [dim]quit[/dim]"
)
# ── 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 _get_all_tags() -> dict[str, list[tuple]]:
"""Build tag → [(tool, category)] index from all tools."""
import re
_rules = {
r'(osint|harvester|maigret|holehe|spiderfoot|sherlock|recon)': 'osint',
r'(subdomain|subfinder|amass|sublist|subdomainfinder)': 'recon',
r'(scanner|scan|nmap|masscan|rustscan|nikto|nuclei|trivy)': 'scanner',
r'(brute|gobuster|ffuf|dirb|dirsearch|ferox|hashcat|john|kerbrute)': 'bruteforce',
r'(web|http|proxy|zap|xss|sql|wafw00f|arjun|caido|mitmproxy)': 'web',
r'(wireless|wifi|wlan|airgeddon|bettercap|wifite|fluxion|deauth)': 'wireless',
r'(phish|social.media|evilginx|setoolkit|social.fish|social.engineer)': 'social-engineering',
r'(c2|sliver|havoc|mythic|pwncat|reverse.shell|pyshell)': 'c2',
r'(privesc|peass|linpeas|winpeas)': 'privesc',
r'(tunnel|pivot|ligolo|chisel|proxy|anon)': 'network',
r'(password|credential|hash|crack|secret|trufflehog|gitleaks)': 'credentials',
r'(forensic|memory|volatility|binwalk|autopsy|wireshark|pspy)': 'forensics',
r'(reverse.eng|ghidra|radare|jadx|androguard|apk)': 'reversing',
r'(cloud|aws|azure|gcp|kubernetes|prowler|scout|pacu)': 'cloud',
r'(mobile|android|ios|frida|mobsf|objection|droid)': 'mobile',
r'(active.directory|bloodhound|netexec|impacket|responder|certipy|kerberos|winrm|smb|ldap)': 'active-directory',
r'(ddos|dos|slowloris|goldeneye|ufonet)': 'ddos',
r'(payload|msfvenom|fatrat|venom|stitch|enigma)': 'payload',
r'(crawler|spider|katana|gospider)': 'crawler',
}
tag_index: dict[str, list[tuple]] = {}
for tool, cat in _collect_all_tools():
combined = f"{tool.TITLE} {tool.DESCRIPTION}".lower()
# Manual tags first
tool_tags = set(getattr(tool, "TAGS", []) or [])
# Auto-derive tags from title/description
for pattern, tag in _rules.items():
if re.search(pattern, combined, re.IGNORECASE):
tool_tags.add(tag)
for t in tool_tags:
tag_index.setdefault(t, []).append((tool, cat))
return tag_index
def filter_by_tag():
"""Show available tags, user picks one, show matching tools."""
tag_index = _get_all_tags()
sorted_tags = sorted(tag_index.keys())
# Show tags in a compact grid
console.print(Panel(
" ".join(f"[bold cyan]{t}[/bold cyan]([dim]{len(tag_index[t])}[/dim])" for t in sorted_tags),
title="[bold magenta] Available Tags [/bold magenta]",
border_style="magenta", box=box.ROUNDED, padding=(0, 2),
))
tag = Prompt.ask("[bold cyan]Enter tag[/bold cyan]", default="").strip().lower()
if not tag or tag not in tag_index:
if tag:
console.print(f"[dim]Tag '{tag}' not found.[/dim]")
Prompt.ask("[dim]Press Enter to return[/dim]", default="")
return
matches = tag_index[tag]
table = Table(
title=f"Tools tagged '{tag}'",
box=box.SIMPLE_HEAD, show_lines=True,
)
table.add_column("No.", justify="center", style="bold cyan", width=5)
table.add_column("", width=2)
table.add_column("Tool", style="bold yellow", min_width=20)
table.add_column("Category", style="magenta", min_width=15)
for i, (tool, cat) in enumerate(matches, start=1):
status = "[green]✔[/green]" if tool.is_installed else "[dim]✘[/dim]"
table.add_row(str(i), status, tool.TITLE, cat)
table.add_row("99", "", "Back to main menu", "")
console.print(table)
raw = Prompt.ask("[bold cyan]>[/bold cyan]", default="").strip()
if not raw or raw == "99":
return
try:
idx = int(raw)
except ValueError:
return
if 1 <= idx <= len(matches):
tool, cat = matches[idx - 1]
tool.show_options()
_RECOMMENDATIONS = {
"scan a network": ["scanner", "port-scanner"],
"find subdomains": ["recon"],
"scan for vulnerabilities": ["scanner", "web"],
"crack passwords": ["bruteforce", "credentials"],
"find leaked secrets": ["credentials"],
"phishing campaign": ["social-engineering"],
"post exploitation": ["c2", "privesc"],
"pivot through network": ["network"],
"pentest active directory": ["active-directory"],
"pentest web application": ["web", "scanner"],
"pentest cloud": ["cloud"],
"pentest mobile app": ["mobile"],
"reverse engineer binary": ["reversing"],
"capture wifi handshake": ["wireless"],
"intercept http traffic": ["web", "network"],
"forensic analysis": ["forensics"],
"ddos testing": ["ddos"],
"create payloads": ["payload"],
"find xss vulnerabilities": ["web"],
"brute force directories": ["bruteforce", "web"],
"osint / recon a target": ["osint", "recon"],
"hide my identity": ["network"],
}
def recommend_tools():
"""Show common tasks, user picks one, show matching tools."""
table = Table(
title="What do you want to do?",
box=box.SIMPLE_HEAD,
)
table.add_column("No.", justify="center", style="bold cyan", width=5)
table.add_column("Task", style="bold yellow")
tasks = list(_RECOMMENDATIONS.keys())
for i, task in enumerate(tasks, start=1):
table.add_row(str(i), task.title())
table.add_row("99", "Back to main menu")
console.print(table)
raw = Prompt.ask("[bold cyan]>[/bold cyan]", default="").strip()
if not raw or raw == "99":
return
try:
idx = int(raw)
except ValueError:
return
if 1 <= idx <= len(tasks):
task = tasks[idx - 1]
tag_names = _RECOMMENDATIONS[task]
tag_index = _get_all_tags()
# Collect unique tools across all matching tags
seen = set()
matches = []
for tag in tag_names:
for tool, cat in tag_index.get(tag, []):
if id(tool) not in seen:
seen.add(id(tool))
matches.append((tool, cat))
if not matches:
console.print("[dim]No tools found for this task.[/dim]")
Prompt.ask("[dim]Press Enter to return[/dim]", default="")
return
console.print(Panel(
f"[bold]Recommended tools for: {task.title()}[/bold]",
border_style="green", box=box.ROUNDED,
))
rtable = Table(box=box.SIMPLE_HEAD, show_lines=True)
rtable.add_column("No.", justify="center", style="bold cyan", width=5)
rtable.add_column("", width=2)
rtable.add_column("Tool", style="bold yellow", min_width=20)
rtable.add_column("Category", style="magenta")
for i, (tool, cat) in enumerate(matches, start=1):
status = "[green]✔[/green]" if tool.is_installed else "[dim]✘[/dim]"
rtable.add_row(str(i), status, tool.TITLE, cat)
rtable.add_row("99", "", "Back", "")
console.print(rtable)
raw2 = Prompt.ask("[bold cyan]>[/bold cyan]", default="").strip()
if raw2 and raw2 != "99":
try:
ridx = int(raw2)
if 1 <= ridx <= len(matches):
matches[ridx - 1][0].show_options()
except ValueError:
pass
def search_tools(query: str | None = None):
"""Search tools — accepts inline query or prompts for one."""
if query is None:
query = Prompt.ask("[bold cyan]/ Search[/bold cyan]", default="").strip().lower()
else:
query = query.lower()
if not query:
return
all_tool_list = _collect_all_tools()
# Match against title + description + tags
matches = []
for tool, category in all_tool_list:
title = (tool.TITLE or "").lower()
desc = (tool.DESCRIPTION or "").lower()
tags = " ".join(getattr(tool, "TAGS", []) or []).lower()
if query in title or query in desc or query in tags:
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():
while True:
try:
build_menu()
raw = Prompt.ask(
"[bold magenta]╰─>[/bold magenta]", default=""
).strip()
if not raw:
continue
raw_lower = raw.lower()
if raw_lower in ("?", "help"):
show_help()
continue
if raw.startswith("/"):
# Inline search: /subdomain → search immediately
query = raw[1:].strip()
search_tools(query=query if query else None)
continue
if raw_lower in ("s", "search"):
search_tools()
continue
if raw_lower in ("t", "tag", "tags", "filter"):
filter_by_tag()
continue
if raw_lower in ("r", "rec", "recommend"):
recommend_tools()
continue
if raw_lower in ("q", "quit", "exit"):
console.print(Panel(
"[bold white on magenta] Goodbye — Come Back Safely [/bold white on magenta]",
box=box.HEAVY, border_style="magenta",
))
break
try:
choice = int(raw_lower)
except ValueError:
console.print("[red]⚠ Invalid input — enter a number, /query to search, or q to quit.[/red]")
Prompt.ask("[dim]Press Enter to continue[/dim]", default="")
continue
if 1 <= choice <= len(all_tools):
title, icon, _ = tool_definitions[choice - 1]
console.print(Panel(
f"[bold magenta]{icon} {title}[/bold magenta]",
border_style="magenta", box=box.ROUNDED,
))
try:
all_tools[choice - 1].show_options()
except Exception as e:
console.print(Panel(
f"[red]Error while opening {title}[/red]\n{e}",
border_style="red",
))
Prompt.ask("[dim]Press Enter to return to main menu[/dim]", default="")
else:
console.print(f"[red]⚠ Choose 1{len(all_tools)}, ? for help, or q to quit.[/red]")
Prompt.ask("[dim]Press Enter to continue[/dim]", default="")
except KeyboardInterrupt:
console.print("\n[bold red]Interrupted — exiting[/bold red]")
break
# ── Entry point ────────────────────────────────────────────────────────────────
def main():
try:
from os_detect import CURRENT_OS
if CURRENT_OS.system == "windows":
console.print(Panel("[bold red]Please run this tool on Linux or macOS.[/bold red]"))
if Confirm.ask("Open guidance link in your browser?", default=True):
webbrowser.open_new_tab(f"{REPO_WEB_URL}#windows")
return
if CURRENT_OS.system not in ("linux", "macos"):
console.print(f"[yellow]Unsupported OS: {CURRENT_OS.system}. Proceeding anyway...[/yellow]")
get_tools_dir() # ensures ~/.hackingtool/tools/ exists
interact_menu()
except KeyboardInterrupt:
console.print("\n[bold red]Exiting...[/bold red]")
if __name__ == "__main__":
main()