{"id":51436910,"url":"https://github.com/edglz/portscout","last_synced_at":"2026-07-05T07:30:26.933Z","repository":{"id":369148280,"uuid":"1288585625","full_name":"edglz/portscout","owner":"edglz","description":"A friendly, graphical terminal UI for nmap — real-time progress bars, live logs, notifications, and a clean hexagonal architecture. Built with Python + Textual.","archived":false,"fork":false,"pushed_at":"2026-07-03T19:34:31.000Z","size":1465,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-03T21:25:39.017Z","etag":null,"topics":["clean-architecture","cli","cybersecurity","hexagonal-architecture","network-scanner","network-security","nmap","pentesting","port-scanner","ports-and-adapters","python","security-tools","terminal","textual","tui"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edglz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"buy_me_a_coffee":"edggdev","custom":["https://buymeacoffee.com/edggdev"]}},"created_at":"2026-07-03T18:51:34.000Z","updated_at":"2026-07-03T19:41:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/edglz/portscout","commit_stats":null,"previous_names":["edglz/portscout"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/edglz/portscout","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edglz%2Fportscout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edglz%2Fportscout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edglz%2Fportscout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edglz%2Fportscout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edglz","download_url":"https://codeload.github.com/edglz/portscout/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edglz%2Fportscout/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35147199,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-architecture","cli","cybersecurity","hexagonal-architecture","network-scanner","network-security","nmap","pentesting","port-scanner","ports-and-adapters","python","security-tools","terminal","textual","tui"],"created_at":"2026-07-05T07:30:26.329Z","updated_at":"2026-07-05T07:30:26.927Z","avatar_url":"https://github.com/edglz.png","language":"Python","funding_links":["https://buymeacoffee.com/edggdev"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Portscout\n\n### A friendly, graphical terminal UI for [nmap](https://nmap.org/)\n\nPoint, press a key, and scan — with real-time progress bars, live logs, desktop\nnotifications, and a clean [hexagonal architecture](#architecture) under the hood.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/)\n[![Built with Textual](https://img.shields.io/badge/TUI-Textual-5e3ca5.svg)](https://textual.textualize.io/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)\n[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-FFDD00.svg?logo=buymeacoffee\u0026logoColor=black)](https://buymeacoffee.com/edggdev)\n\n\u003c/div\u003e\n\n---\n\n\u003e **Responsible use.** Portscout is a front-end for nmap intended for **authorized**\n\u003e network administration, security auditing, and education only. Scanning systems you\n\u003e do not own or lack written permission to test may be illegal in your jurisdiction.\n\u003e You accept full and sole responsibility for your use of this tool. See\n\u003e [`DISCLAIMER.md`](DISCLAIMER.md). The application performs no scan until you have\n\u003e read and accepted these terms on first launch.\n\n## Overview\n\nPortscout wraps the power of nmap in an approachable terminal user interface. It is\naimed at people who want nmap's capabilities without memorizing its flags, as well as\nexperienced users who appreciate a fast, keyboard-driven dashboard with live feedback.\n\nIt is also a small, readable reference implementation of the **ports and adapters\n(hexagonal) architecture** in Python — the scanning core is completely decoupled from\nnmap, the filesystem, and the UI framework.\n\n## Features\n\n- **Graphical TUI** built with [Textual](https://textual.textualize.io/); runs in any\n  modern terminal.\n- **Real progress bars** driven by nmap's own live statistics (the `About XX% done`\n  timing lines), not a fake spinner.\n- **Live logs** streamed line-by-line while the scan runs.\n- **Notifications** for scan start, completion, errors, and missing dependencies.\n- **Scan profiles** — ping sweep, quick, standard (service/version detection), full\n  TCP with scripts and OS detection, and UDP top ports.\n- **Live command preview** — see the exact `nmap` command that will run, updated as you\n  type, before you launch it.\n- **Results table** — hosts, ports, states, and detected services at a glance.\n- **Configuration** — persisted to a TOML file; edit the nmap path, default profile,\n  timeout, and output directory from within the app.\n- **Selectable themes** — a dozen predefined themes (Nord, Gruvbox, Dracula, Tokyo\n  Night, Catppuccin, Solarized, and more) that preview live and persist between runs.\n- **Dependency doctor** — checks that nmap and libpcap/npcap are present and guides\n  installation when they are not.\n- **Clean hexagonal architecture** — the domain core has zero third-party imports and\n  is fully unit-tested.\n\n## Demo\n\n\u003cdiv align=\"center\"\u003e\n\n![Portscout in action: accepting the disclaimer, entering a target, and watching a live scan](docs/demo.gif)\n\n*Accept the responsible-use disclaimer, type a target, and watch the live progress,\nstreaming log, and results table.*\n\n\u003c/div\u003e\n\n### More screens\n\n\u003cdiv align=\"center\"\u003e\n\n**Live input validation and command preview**\n\n![Live validation](docs/preview.gif)\n\n**Dependency doctor** \u0026nbsp;·\u0026nbsp; **Settings**\n\n\u003cimg src=\"docs/deps.gif\" width=\"49%\" alt=\"Dependency doctor\" /\u003e\n\u003cimg src=\"docs/settings.gif\" width=\"49%\" alt=\"Settings screen\" /\u003e\n\n**Selectable themes with live preview**\n\n![Theme switching](docs/themes.gif)\n\n\u003c/div\u003e\n\n## Requirements\n\nPortscout is a front-end, so it needs the `nmap` binary and its packet-capture library\ninstalled on your system:\n\n| Platform | Install command |\n| --- | --- |\n| macOS | `brew install nmap` (bundles libpcap) |\n| Debian / Ubuntu | `sudo apt install nmap libpcap0.8` |\n| Fedora / RHEL | `sudo dnf install nmap libpcap` |\n| Arch | `sudo pacman -S nmap libpcap` |\n| Windows | Install [Nmap](https://nmap.org/download.html) and [Npcap](https://npcap.com/) |\n\nPlus **Python 3.11 or newer**.\n\n## Installation\n\nFrom source (recommended while pre-release):\n\n```bash\ngit clone https://github.com/edglz/portscout.git\ncd portscout\npython -m venv .venv \u0026\u0026 source .venv/bin/activate   # Windows: .venv\\Scripts\\activate\npip install -e .\nportscout\n```\n\nOr run without installing:\n\n```bash\npip install -r requirements.txt\npython -m portscout\n```\n\n\u003e Privileged profiles (full TCP SYN scan, UDP scan) require raw sockets. Launch with\n\u003e `sudo portscout` on Linux/macOS, or run as Administrator on Windows, to use them.\n\n## Keyboard shortcuts\n\n| Key | Action |\n| --- | --- |\n| `Enter` | Start scan (while the target/ports field is focused) |\n| `s` | Start scan (when no text field is focused) |\n| `x` | Stop the running scan |\n| `c` | Open settings |\n| `d` | Show dependency status |\n| `Ctrl+L` | Clear the log |\n| `q` | Quit |\n\n## Configuration\n\nSettings are stored in a TOML file (its exact path is shown in the in-app settings\nscreen):\n\n- Linux / macOS: `~/.config/portscout/config.toml`\n- Windows: `%APPDATA%\\portscout\\config.toml`\n\n```toml\ndefault_profile = \"standard\"\nnmap_path = \"nmap\"\noutput_dir = \"~/scans\"     # if set, each scan's raw XML is written here\ntimeout_seconds = 600\ntheme = \"textual-dark\"\ndisclaimer_accepted = true\n```\n\n## Architecture\n\nPortscout follows the **hexagonal (ports and adapters)** pattern. The business core\nnever imports a framework or spawns a subprocess; it depends only on abstract *ports*.\nConcrete *adapters* implement those ports and are wired together in a single\ncomposition root.\n\n```\nsrc/portscout/\n├── domain/          Pure core: value objects, entities, port interfaces (no third-party deps)\n│   ├── value_objects.py   Target, PortSpec, ScanProfile (self-validating)\n│   ├── entities.py        ScanRequest, Host, Port, Service, ScanResult\n│   └── ports.py           ScannerPort, ConfigPort, DependencyCheckerPort\n├── application/     Use cases orchestrating the domain through the ports\n│   └── services.py        ScanService, ConfigService, DependencyService\n├── infrastructure/  Adapters implementing the ports (the driven side)\n│   ├── nmap_adapter.py    Runs nmap and parses its XML        -\u003e ScannerPort\n│   ├── config.py          TOML persistence                    -\u003e ConfigPort\n│   └── dependencies.py    System dependency detection         -\u003e DependencyCheckerPort\n├── presentation/    Textual TUI (the driving side)\n│   ├── app.py             Widgets, worker threads, progress, notifications\n│   ├── screens.py         Disclaimer / Settings / Dependencies modal screens\n│   └── styles.tcss        Styling\n└── bootstrap.py     Composition root: the only place adapters are instantiated\n```\n\n**Why it matters.** You could swap nmap for `masscan`, or TOML for a database, by\nwriting one new adapter and changing `bootstrap.py` — nothing in `domain/` or\n`application/` would change. The same decoupling is what makes the core trivial to\nunit-test without touching the network.\n\n### Data flow of a scan\n\n1. The TUI collects raw input (target, profile, ports) and asks `ScanService` to build\n   a validated `ScanRequest`. Invalid input is rejected by the value objects before any\n   process is spawned.\n2. `ScanService` delegates to whatever `ScannerPort` was injected — here, `NmapAdapter`.\n3. `NmapAdapter` invokes nmap with an argument list (never a shell string), streams its\n   output back through a progress callback, and parses the resulting XML into domain\n   entities.\n4. The TUI renders progress and, on completion, the `ScanResult` table.\n\n## Security notes\n\n- nmap is always invoked with an argument **list** and never `shell=True`, and the\n  target is validated by the `Target` value object first, so there is no\n  shell-injection surface.\n- Portscout never elevates privileges silently. Privileged profiles fail with a clear\n  message asking you to relaunch with elevated permissions.\n\n## Development\n\n```bash\npip install -e \".[dev]\"\npytest            # run the test suite\nruff check .      # lint\nmypy              # type-check\ntextual run --dev portscout.presentation.app:PortscoutApp   # hot-reload dev mode\n```\n\n## Contributing\n\nContributions are very welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) and the\n[Code of Conduct](CODE_OF_CONDUCT.md) before opening a pull request. Issues labeled\n[`good first issue`](https://github.com/edglz/portscout/labels/good%20first%20issue) are\na good place to start.\n\nIf Portscout is useful to you, please consider starring the repository — it genuinely\nhelps the project reach more people.\n\n## Support\n\nIf Portscout saves you time, you can support its development:\n\n\u003ca href=\"https://buymeacoffee.com/edggdev\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-FFDD00.svg?logo=buymeacoffee\u0026logoColor=black\" alt=\"Buy Me A Coffee\" /\u003e\n\u003c/a\u003e\n\nStarring the repository also helps a lot.\n\n## License\n\n[MIT](LICENSE) © Portscout contributors.\n\n## Acknowledgements\n\n- [nmap](https://nmap.org/) — the engine this is a friendly face for.\n- [Textual](https://textual.textualize.io/) — the TUI framework.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedglz%2Fportscout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedglz%2Fportscout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedglz%2Fportscout/lists"}