nettopo
A Python CLI that reads saved Cisco show command outputs — or collects them itself
over SSH — and generates network diagrams in draw.io format with Cisco icons,
plus the intermediate data as CSV tables.
Four diagram views are produced from the same parsed data model:
- L2 — physical/link-layer topology from CDP/LLDP, with interface labels and MLAG.
- STP — per-VLAN Rapid-PVST spanning-tree state (root, bridge IDs, priorities, port roles/states).
- HSRP — first-hop redundancy per SVI (virtual IP, priority, active/standby/listen).
- BGP — BGP neighbor (session) graph (AS numbers, session state, iBGP vs eBGP).
Hand-drawn network diagrams drift out of date the moment the network changes. With
nettopo the diagrams are generated from what the devices actually report, so refreshing
the documentation is a matter of re-running two commands.
nettopois a working title — seePROJECT_SPEC.mdsection 1.
Status
All eight phases of the
delivery plan are delivered:
the four diagram views, the CSV export, all
(everything in one pass over a single ingest), and
collect (read-only live
collection over SSH). See CHANGELOG.md for the release history.
draw.io only. The Cisco
mxgraph.cisco19.*icons are drawn by draw.io's own code and no other tool implements them, so these files are meant to be opened in draw.io. Lucidchart export was tried and dropped —docs/architecture.mdrecords why.
Installation
pip install nettopo
That is everything, live collection over SSH included.
Pre-releases exist too; pip skips them unless asked (pip install --pre nettopo).
To install from source for development:
git clone https://github.com/netcraftworks/nettopo.git
cd nettopo
pip install -e ".[dev]"
Requires Python 3.11+.
Quickstart
nettopo collect --inventory devices.txt # devices -> ~/configs, over SSH
nettopo all # ~/configs -> ./output
The inventory is a plain text file naming one device per line — hostname, FQDN or IP —
with # starting a comment. No credentials, no groups, no per-device variables:
# devices.txt — everything nettopo collect needs to know
core-sw1
core-sw2
10.0.20.7 # bldg-a access stack, management address
rtr-edge.example.net
collect asks for the credentials on the terminal, sends nothing but show commands,
and writes one capture file per device into ~/configs — the directory every other
command reads by default, so -i can be dropped entirely. Every run also writes
nettopo-collect-report.csv to the current directory, with one row per inventory entry
so a device that was never reached is as visible as one that was collected:
target,hostname,status,platform,commands,capture_file,detail
core-sw1,core-sw1,ok,cisco_ios,10,/home/you/configs/core-sw1.txt,
core-sw2,core-sw2,ok,cisco_ios,10,/home/you/configs/core-sw2.txt,
10.0.20.7,acc-sw2,ok,cisco_nxos,9,/home/you/configs/acc-sw2.txt,
rtr-edge.example.net,,failed,,0,,NetmikoAuthenticationException: Authentication to device failed
If you already have captures saved by hand, skip collect and point -i at them (see
Preparing Captures
for the file format). all then writes the complete output tree:
output/
├── csv/ devices, interfaces, neighbors, vlans, stp, hsrp, bgp
├── l2/ l2_full.drawio, l2_full_port-channels.drawio, l2_network-only.drawio
├── stp/ one diagram per VLAN
├── hsrp/ one diagram per VLAN
└── bgp/ bgp.drawio
Commands
nettopo [--version] [--log-level LEVEL] <command> [options]
Every command reads a directory of capture files (-i, default ~/configs) and writes
into an output directory (-o, default ./output). Each links to its full reference in
the wiki:
| Command | What it does |
|---|---|
collect |
Gathers the captures from the devices over SSH — read-only, serial, stops at the first error |
parse |
CSV tables only, no diagrams — the primary debugging aid |
l2 |
Physical topology from CDP/LLDP, with --endpoints and --link-mode filtering |
stp |
Per-VLAN spanning-tree diagrams, with --group-mode to collapse identical trees |
hsrp |
Per-VLAN first-hop-redundancy diagrams |
bgp |
The BGP session graph for the whole network |
all |
Every view and every CSV in one run over a single ingest |
Example diagrams
Every image is nettopo's own output, generated from the runnable capture sets in
examples/; the .drawio files are committed next to the captures. The full
six-diagram walkthrough is in the
Example Gallery — here
are three of them.
nettopo l2 — the campus example: ten devices, thirteen links, each end labeled with
its physical interface, each node drawn with the Cisco icon for its inferred role.
nettopo stp --vlan 10 — the same network's spanning tree: core-sw1 highlighted as
root, green links forwarding, red links with a blocked end, and the uncaptured acc-sw3
drawn faded.
nettopo hsrp --vlan 50 — a four-member HSRP group: the virtual gateway in the
middle, the active router on a green link, the standby on amber, and two listeners on
neutral gray.
Documentation
- Wiki — full command reference, capture preparation, and the example gallery.
examples/— runnable capture sets, including the ones behind the diagrams above.PROJECT_SPEC.md— scope, architecture, data model, CLI, delivery plan.docs/architecture.md— components, call flow, design decisions.CLAUDE.md— engineering conventions (workflow, coding principles, security review).CHANGELOG.md— release history.
Getting help
- The wiki holds the detailed documentation: the full reference for every command, the capture file format, and the example gallery.
- For bugs or unexpected output, open an
issue. Attaching the CSV tables from
nettopo parseusually pinpoints where an ingest went wrong.
Contributing and maintainers
nettopo is maintained by netcraftworks.
Contributions are welcome: work is tracked as GitHub issues, and each change lands via a
pull request from a feat/…/fix/… branch into main; direct pushes to main are
disabled. See CLAUDE.md for the full workflow and coding conventions.
Security
Every command except collect makes zero network connections by design — they only
read local capture files. That boundary is enforced rather than asserted:
tests/test_no_network.py monkeypatches socket.socket to fail and runs every other
command end to end, and asserts in a fresh interpreter that importing the CLI never
imports the SSH backend, so it stays reachable only through collect's own handler.
collect, the one command that opens a socket, sends nothing but show commands
(checked at the moment of sending), accepts no password on the command line, stores no
credential anywhere, requires known SSH host keys unless you opt out, and stops at the
first error so a mistyped password costs one failed authentication attempt, not a
fleet-wide lockout. Details in
its wiki page; the full
OWASP-adapted review is
PROJECT_SPEC.md §11.
License
Metadata
Release files for nettopo 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nettopo-0.9.0.tar.gz | 123.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nettopo-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 213.1 kB
Release files / nettopo-0.9.0.tar.gz
| Download URL | nettopo-0.9.0.tar.gz |
|---|---|
| Size | 123.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a22a985877057fa7606963f4f3cc883d8941338d9eefab30fe4a2cce3797779c
|
|
BLAKE2b-256 checksum How to use checksums |
3ea4a1703262274d8b68f37bfab8e924d9c3977706cfdcb3d812f04aca708cfb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency logRelease files / nettopo-0.9.0-py3-none-any.whl
| Download URL | nettopo-0.9.0-py3-none-any.whl |
|---|---|
| Size | 90.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b7c5700fce0a64c341b445866143aeee31d490b4e86bf0d4c70fc524283bb21b
|
|
BLAKE2b-256 checksum How to use checksums |
f6e38a6da419f9f29b0441a37d62a0a4b147cb712f87a38bf12b5f8a5a839f81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency log