Modern SSH terminal widget for PyQt6 with credential vault and jump host support
Project description
nterm
A modern SSH terminal for network engineers
PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiKey/FIDO2 support, and legacy device compatibility.
Built for managing hundreds of devices through bastion hosts with hardware security keys.
Features
Terminal
- xterm.js rendering via QWebEngineView — full VT100/ANSI support
- 12 built-in themes: Catppuccin, Dracula, Nord, Solarized, Gruvbox, Enterprise variants
- Hybrid themes: dark UI chrome with light terminal for readability
- Custom YAML themes with independent terminal and UI colors
- Tab or window per session — pop sessions to separate windows
- Session capture to file (clean text, ANSI stripped)
- Unicode, emoji, box-drawing characters
Authentication
- SSH Agent with YubiKey/FIDO2 hardware keys
- Password, key file, keyboard-interactive, certificate auth
- Multiple auth methods with automatic fallback
- RSA SHA-1 fallback for legacy devices (OpenSSH < 7.2)
- Legacy crypto support for old Juniper/Cisco gear
Connection Management
- Jump host chaining (unlimited hops)
- Auto-reconnection with exponential backoff
- Connection profiles in YAML/JSON
- Pattern-based credential resolution
Credential Vault
- AES-256 encryption with PBKDF2 (480,000 iterations)
- Pattern matching — map credentials to hosts by wildcard or tag
- Cross-platform keychain: macOS Keychain, Windows Credential Locker, Linux Secret Service
- Full PyQt6 management UI
Scripting API
- Query device inventory and credentials programmatically
- Built-in IPython console with API pre-loaded
- Platform-aware commands - one API, correct syntax everywhere
- Interactive REPL with quick commands and structured output
- Foundation for MCP tools and agentic workflows
Screenshots
| Gruvbox Hybrid Theme | Credential Manager |
|---|---|
| Connection Dialog | Multi-vendor Support |
|---|---|
Dev Console
nterm includes a built-in development console accessible via Dev → IPython or Dev → Shell. Open in a tab alongside your SSH sessions, or pop out to a separate window.
The IPython console runs in the same Python environment as nterm, with the scripting API pre-loaded. Query your device inventory, inspect credentials, and prototype automation workflows without leaving the app.
# Available immediately when IPython opens
api.devices() # List all saved devices
api.search("leaf") # Search by name/hostname
api.credentials() # List credentials (after api.unlock())
api.help() # Show all commands
Use cases:
- Debug connection issues with live access to session objects
- Prototype automation scripts against your real device inventory
- Test credential resolution patterns
- Build and test MCP tools interactively
Requires the scripting extra: pip install ntermqt[scripting]
Installation
Be aware due to a naming conflict, the pypi package is actually "ntermqt"
From PyPI
https://pypi.org/project/ntermqt/
pip install ntermqt
# With optional scripting support (IPython)
pip install ntermqt[scripting]
# With all optional features
pip install ntermqt[all]
# Run
nterm
From Source
git clone https://github.com/scottpeterman/nterm.git
cd nterm
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# Install in development mode
pip install -e ".[all]"
# Run
nterm
# or
python -m nterm
Requirements
- Python 3.10+
- PyQt6 with WebEngine
- paramiko
- cryptography
- pyyaml
Platform Support
| Platform | PTY | Keychain |
|---|---|---|
| Linux | ✅ pexpect | Secret Service |
| macOS | ✅ pexpect | macOS Keychain |
| Windows 10+ | ✅ pywinpty | Credential Locker |
Scripting API
nterm includes a full scripting API for programmatic access to your device inventory, credential vault, and network devices. Use it from IPython, CLI, or Python scripts.
IPython Console
Open Dev → IPython → Open in Tab to get an interactive console with the API pre-loaded:
api.devices() # List all saved devices
api.search("leaf") # Search by name/hostname
api.devices("eng-*") # Glob pattern filter
api.folders() # List all folders
api.unlock("vault-password") # Unlock credential vault
api.credentials() # List credentials (metadata only)
# Connect and execute commands
with api.session("usa-leaf-1") as s:
result = api.send(s, "show version")
print(result.parsed_data)
api.help() # Show all commands
Interactive REPL
Start the REPL for interactive device exploration with platform-aware quick commands:
api.repl()
nterm> :unlock
nterm> :connect usa-leaf-1
📊 usa-leaf-1> :version
──────────────────────────────────────────────────
Version: 15.2(4.0.55)E
Hardware: IOSv
Serial: 9J0PD0QB9W1
Uptime: 1 week, 4 days, 7 minutes
──────────────────────────────────────────────────
📊 usa-leaf-1> :neighbors
Local Interface Neighbor Remote Port
----------------------------------------------------------------
Gi0/0 usa-spine-2.lab.local Ethernet1
Gi0/1 usa-spine-1.lab.local Ethernet1
📊 usa-leaf-1> :interfaces
[Rich formatted interface table]
Quick Commands:
:version- Structured version info:config- Running configuration:interfaces- Interface status:neighbors- CDP/LLDP neighbors (auto-detects):bgp- BGP summary:routes- Routing table
Python Scripts
from nterm.scripting import NTermAPI
api = NTermAPI()
api.unlock("vault-password")
# Context manager for automatic cleanup
for device in api.devices("*spine*"):
with api.session(device.name) as s:
# Platform-aware commands - works on Cisco, Arista, Juniper
result = api.send_platform_command(s, 'version')
print(f"{device.name}: {result.parsed_data[0].get('VERSION')}")
# Try multiple commands until one works
with api.session("router1") as s:
result = api.send_first(s, [
"show cdp neighbors detail",
"show lldp neighbors detail",
])
CLI
nterm-cli devices # List all devices
nterm-cli search leaf # Search devices
nterm-cli device eng-leaf-1 # Device details
nterm-cli credentials --unlock # List credentials
nterm-cli --json devices # JSON output for scripting
Key Features
| Feature | Description |
|---|---|
| Context Manager | with api.session() auto-disconnects |
| Platform-Aware | send_platform_command() picks correct syntax |
| Fallback Commands | send_first() tries alternatives |
| Structured Output | TextFSM parsing to List[Dict] |
| ANSI Filtering | Clean output, no escape sequences |
| Paging Detection | Raises error if paging not disabled |
See scripting/README_API_IPython.md for full API documentation. See scripting/README_REPL.md for REPL documentation.
Quick Start
As a Widget
from PyQt6.QtWidgets import QApplication, QMainWindow
from nterm import ConnectionProfile, AuthConfig, SSHSession, TerminalWidget, Theme
app = QApplication([])
terminal = TerminalWidget()
terminal.set_theme(Theme.gruvbox_hybrid())
profile = ConnectionProfile(
name="router",
hostname="192.168.1.1",
auth_methods=[AuthConfig.password_auth("admin", "secret")],
)
session = SSHSession(profile)
terminal.attach_session(session)
session.connect()
window = QMainWindow()
window.setCentralWidget(terminal)
window.resize(1000, 700)
window.show()
app.exec()
With Credential Vault
from nterm.vault import CredentialStore, CredentialResolver
store = CredentialStore()
store.unlock("master-password")
# Add credential with pattern matching
store.add_credential(
name="network-devices",
username="admin",
password="secret",
match_hosts=["*.network.corp", "192.168.1.*"],
match_tags=["cisco", "juniper"],
)
# Auto-resolve credentials by hostname
resolver = CredentialResolver(store)
profile = resolver.resolve_for_device("switch01.network.corp", tags=["cisco"])
session = SSHSession(profile)
session.connect()
Themes
nterm includes 12 built-in themes covering dark, light, and hybrid styles.
Built-in Themes
# Dark themes
Theme.default() # Catppuccin Mocha
Theme.dracula() # Dracula
Theme.nord() # Nord
Theme.solarized_dark() # Solarized Dark
Theme.gruvbox_dark() # Gruvbox Dark
Theme.enterprise_dark() # Microsoft-inspired dark
# Light themes
Theme.gruvbox_light() # Gruvbox Light
Theme.enterprise_light() # Microsoft-inspired light
Theme.clean() # Warm paper tones
# Hybrid themes (dark UI + light terminal)
Theme.gruvbox_hybrid() # Gruvbox dark chrome, light terminal
Theme.nord_hybrid() # Nord polar night chrome, snow storm terminal
Theme.enterprise_hybrid() # VS Code-style dark/light split
Hybrid themes combine a dark application chrome (menus, tabs, sidebars) with a light terminal for maximum readability — ideal for long sessions reviewing configs or logs.
Custom YAML Themes
# ~/.nterm/themes/my-theme.yaml
name: my-theme
terminal_colors:
background: "#1a1b26"
foreground: "#c0caf5"
cursor: "#c0caf5"
black: "#15161e"
red: "#f7768e"
green: "#9ece6a"
yellow: "#e0af68"
blue: "#7aa2f7"
magenta: "#bb9af7"
cyan: "#7dcfff"
white: "#a9b1d6"
# ... bright variants
# UI chrome (can differ from terminal)
background_color: "#1a1b26"
foreground_color: "#c0caf5"
border_color: "#33467c"
accent_color: "#7aa2f7"
Session Capture
Capture session output to a file for documentation, auditing, or extracting config snippets.
Right-click in terminal → Start Capture... to begin recording. Output is saved as clean text with ANSI escape sequences stripped — ready for grep, diff, or pasting into tickets.
- Per-session capture (each tab independent)
- File dialog for save location
- Menu shows active capture filename
- Auto-stops when session closes
Jump Hosts
profile = ConnectionProfile(
name="internal-db",
hostname="db01.internal.corp",
auth_methods=[AuthConfig.agent_auth("dbadmin")],
jump_hosts=[
JumpHostConfig(
hostname="bastion.corp.com",
auth=AuthConfig.agent_auth("youruser"),
requires_touch=True,
touch_prompt="Touch YubiKey for bastion...",
),
],
)
Legacy Device Support
nterm automatically handles old network equipment:
- RSA SHA-1 fallback for OpenSSH < 7.2 servers
- Legacy KEX algorithms: diffie-hellman-group14-sha1, group1-sha1
- Legacy ciphers: aes128-cbc, 3des-cbc
- Auto-detection: tries modern crypto first, falls back as needed
Tested with:
- Junos 14.x (2016)
- Cisco IOS 12.2
- Old Arista EOS
- Any device running OpenSSH 6.x
Architecture
nterm/
├── connection/ # ConnectionProfile, AuthConfig, JumpHostConfig
├── session/
│ ├── ssh.py # SSHSession (Paramiko) with legacy fallback
│ ├── interactive_ssh.py # Native SSH + PTY
│ ├── local_terminal.py # Local shell/IPython sessions
│ └── pty_transport.py # Cross-platform PTY
├── terminal/
│ ├── widget.py # TerminalWidget (PyQt6 + xterm.js)
│ └── bridge.py # Qt ↔ JavaScript bridge
├── theme/
│ ├── engine.py # Theme system
│ └── themes/ # YAML theme files
├── vault/
│ ├── store.py # Encrypted credential storage
│ ├── resolver.py # Pattern-based resolution
│ └── manager_ui.py # PyQt6 credential manager
├── manager/ # Session tree, connection dialogs
└── scripting/ # API, REPL, automation support
├── api.py # NTermAPI class
├── models.py # ActiveSession, CommandResult, DeviceInfo
├── platform_data.py # Platform commands and patterns
├── platform_utils.py # Platform detection, extraction helpers
├── ssh_connection.py # Low-level SSH with ANSI filtering
├── repl.py # NTermREPL command router
└── repl_interactive.py # Interactive REPL display
Related Projects
- TerminalTelemetry — PyQt6 terminal with network monitoring
- Secure Cartography — Network discovery and mapping
License
GPLv3
Contributing
Contributions welcome:
- Additional themes
- Windows testing
- Session recording/playback
- Telnet/serial support
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ntermqt-0.1.12.tar.gz.
File metadata
- Download URL: ntermqt-0.1.12.tar.gz
- Upload date:
- Size: 236.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9cf53af23d36a95a0a7f47479e931ded6ad50e22a5417e128d54c35d971b6473
|
|
| MD5 |
a1165c88a73fa2fa4141be90a3c78984
|
|
| BLAKE2b-256 |
ac72ed40756799daff4dc1f52e4ec37abd506ffe4876272e8a9f5656f04b37df
|
File details
Details for the file ntermqt-0.1.12-py3-none-any.whl.
File metadata
- Download URL: ntermqt-0.1.12-py3-none-any.whl
- Upload date:
- Size: 261.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
daafe8cc9f359274010da1c08fb7185e35f6554bcd99759d7a36593a8881875d
|
|
| MD5 |
c1a58f2537d039e6088239c5fa3d696c
|
|
| BLAKE2b-256 |
1a4f6c0e0788f7681485f20f01c6814036d66b5a457eeb3b58237c00b11a0584
|