mirror of
https://github.com/Z4nzu/hackingtool.git
synced 2026-11-04 05:02:29 +00:00
Interactive terminal UI (Textual, default on a TTY) - Category browser with live tool counts; type to search EVERY tool across all categories; up/down move, Enter open, left/Esc back; `/` opens a command palette. - Tool screen gates Run/Install/Uninstall behind a confirm, dropping to a real TTY so a tool can't crash the UI; Open-repo is http(s)-guarded. - The engine is unchanged; `--classic` keeps the full prompt_toolkit REPL and the headless `--engagement` orchestrator is untouched. Update awareness + self-update helper - Cached-daily GitHub Releases check (offline-safe, opt-out via HACKINGTOOL_NO_UPDATE_CHECK / update_check config); shows an "update available" line on the main screen via a background worker (never blocks first render). - `/update` detects the install method (pipx/pip/apt/docker/source) and shows the exact upgrade command to copy - it never runs it. Native settings + config hygiene - `/update`/`/config` run as in-TUI screens instead of suspending to prompt_toolkit (which raised "event loop already running" nested under Textual). - Sponsor feed URL moved from user config to a fixed code constant (SPONSORS_URL); update_check is now a proper bool toggle. Docker / README - Docker image references aligned to hardikzinzu/hackingtool. - README refreshed for the interactive UI; first screenshot replaced with the new UI. Community-reported bug fixes (verified still present in current code) - Data safety: atomic JSON/YAML writes (#773, #765); malformed config.json no longer crashes load/import (#775); UTF-8 for catalog/settings IO (#766); findings parsers skip non-object/typed records instead of aborting (#768); pipeline decodes invalid bytes and salvages timed-out evidence (#771, #770). - AI layer: reject null/non-text provider responses (#778); auto-mode connection test honors the Ollama fallback (#779); settings stay responsive during the AI probe (#780); one findings snapshot per AI report (#772). - CLI/REPL: `@nmap`/`@sqlmap` resolve canonically instead of fuzzy mis-hits (#767); bare `/` no longer crashes the REPL (#761); bad/incomplete args exit non-zero instead of opening the menu (#763); search/tag result #99 is reachable (#762); background pane creation failures are reported, not shown as "started" (#777); config/API-key save failures surface without closing settings (#776). - Misc: .dockerignore keeps README.md for the build (#781); PortScan prompt markup fixed (#698); uninstall command lines use [warning] not [error] (#666, #663); untrusted-output fence can't be broken out of + script import fix (#757).
68 lines
1.9 KiB
Python
68 lines
1.9 KiB
Python
import asyncio
|
|
|
|
from textual.app import App, ComposeResult
|
|
|
|
from hackingtool.tui.widgets.command_palette import CommandPalette, COMMANDS
|
|
|
|
|
|
class _App(App):
|
|
picked = None
|
|
|
|
def compose(self) -> ComposeResult:
|
|
yield CommandPalette()
|
|
|
|
def on_command_palette_selected(self, e: CommandPalette.Selected) -> None:
|
|
self.picked = e.command
|
|
|
|
|
|
def _ids(pal: CommandPalette) -> list[str]:
|
|
return [pal.get_option_at_index(i).id for i in range(pal.option_count)]
|
|
|
|
|
|
def test_empty_needle_shows_all_commands():
|
|
pal = CommandPalette()
|
|
opts = pal._opts("")
|
|
assert len(opts) == len(COMMANDS) == 6
|
|
assert "/config" in [o.id for o in opts]
|
|
assert "/update" in [o.id for o in opts]
|
|
assert "/search" not in [o.id for o in opts] # type-to-filter replaces it
|
|
|
|
|
|
def test_opts_narrows_by_name():
|
|
pal = CommandPalette()
|
|
assert [o.id for o in pal._opts("conf")] == ["/config"]
|
|
|
|
|
|
def test_opts_narrows_by_help_text():
|
|
pal = CommandPalette()
|
|
assert [o.id for o in pal._opts("export")] == ["/report"] # /report's help
|
|
|
|
|
|
def test_filter_updates_list_and_highlights_first():
|
|
async def run():
|
|
app = _App()
|
|
async with app.run_test() as pilot:
|
|
await pilot.pause()
|
|
pal = app.query_one(CommandPalette)
|
|
assert pal.option_count == len(COMMANDS)
|
|
pal.filter("conf")
|
|
await pilot.pause()
|
|
assert _ids(pal) == ["/config"]
|
|
assert pal.highlighted == 0
|
|
|
|
asyncio.run(run())
|
|
|
|
|
|
def test_selecting_emits_selected():
|
|
async def run():
|
|
app = _App()
|
|
async with app.run_test() as pilot:
|
|
await pilot.pause()
|
|
pal = app.query_one(CommandPalette)
|
|
pal.highlighted = pal.get_option_index("/config")
|
|
pal.action_select() # palette is non-focusable; real UI drives it from the prompt/mouse
|
|
await pilot.pause()
|
|
assert app.picked == "/config"
|
|
|
|
asyncio.run(run())
|