Skip to content

scottpeterman/nterm-qt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

26 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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.

nterm screenshot


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
gruvbox vault
Connection Dialog Multi-vendor Support
connect neofetch

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.

IPython Console

IPython Console

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


License

GPLv3


Contributing

Contributions welcome:

  • Additional themes
  • Windows testing
  • Session recording/playback
  • Telnet/serial support

About

A modern, themeable SSH terminal widget for PyQt6 with enterprise features: encrypted credential vault with GUI manager, jump host chaining, YubiKey/FIDO2 support, cross-platform keychain integration, and auto-reconnection.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors