ntermqt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ntermqt-0.1.0/MANIFEST.in +8 -0
- ntermqt-0.1.0/PKG-INFO +327 -0
- ntermqt-0.1.0/README.md +291 -0
- ntermqt-0.1.0/nterm/__init__.py +54 -0
- ntermqt-0.1.0/nterm/__main__.py +619 -0
- ntermqt-0.1.0/nterm/askpass/__init__.py +22 -0
- ntermqt-0.1.0/nterm/askpass/server.py +393 -0
- ntermqt-0.1.0/nterm/config.py +158 -0
- ntermqt-0.1.0/nterm/connection/__init__.py +17 -0
- ntermqt-0.1.0/nterm/connection/profile.py +296 -0
- ntermqt-0.1.0/nterm/manager/__init__.py +29 -0
- ntermqt-0.1.0/nterm/manager/connect_dialog.py +322 -0
- ntermqt-0.1.0/nterm/manager/editor.py +262 -0
- ntermqt-0.1.0/nterm/manager/io.py +678 -0
- ntermqt-0.1.0/nterm/manager/models.py +346 -0
- ntermqt-0.1.0/nterm/manager/settings.py +264 -0
- ntermqt-0.1.0/nterm/manager/tree.py +493 -0
- ntermqt-0.1.0/nterm/resources.py +48 -0
- ntermqt-0.1.0/nterm/session/__init__.py +60 -0
- ntermqt-0.1.0/nterm/session/askpass_ssh.py +399 -0
- ntermqt-0.1.0/nterm/session/base.py +110 -0
- ntermqt-0.1.0/nterm/session/interactive_ssh.py +522 -0
- ntermqt-0.1.0/nterm/session/pty_transport.py +571 -0
- ntermqt-0.1.0/nterm/session/ssh.py +610 -0
- ntermqt-0.1.0/nterm/terminal/__init__.py +11 -0
- ntermqt-0.1.0/nterm/terminal/bridge.py +83 -0
- ntermqt-0.1.0/nterm/terminal/resources/terminal.html +253 -0
- ntermqt-0.1.0/nterm/terminal/resources/terminal.js +414 -0
- ntermqt-0.1.0/nterm/terminal/resources/xterm-addon-fit.min.js +8 -0
- ntermqt-0.1.0/nterm/terminal/resources/xterm-addon-unicode11.min.js +8 -0
- ntermqt-0.1.0/nterm/terminal/resources/xterm-addon-web-links.min.js +8 -0
- ntermqt-0.1.0/nterm/terminal/resources/xterm.css +209 -0
- ntermqt-0.1.0/nterm/terminal/resources/xterm.min.js +8 -0
- ntermqt-0.1.0/nterm/terminal/widget.py +380 -0
- ntermqt-0.1.0/nterm/theme/__init__.py +10 -0
- ntermqt-0.1.0/nterm/theme/engine.py +456 -0
- ntermqt-0.1.0/nterm/theme/stylesheet.py +377 -0
- ntermqt-0.1.0/nterm/theme/themes/clean.yaml +0 -0
- ntermqt-0.1.0/nterm/theme/themes/default.yaml +36 -0
- ntermqt-0.1.0/nterm/theme/themes/dracula.yaml +36 -0
- ntermqt-0.1.0/nterm/theme/themes/gruvbox_dark.yaml +36 -0
- ntermqt-0.1.0/nterm/theme/themes/gruvbox_hybrid.yaml +38 -0
- ntermqt-0.1.0/nterm/theme/themes/gruvbox_light.yaml +36 -0
- ntermqt-0.1.0/nterm/vault/__init__.py +32 -0
- ntermqt-0.1.0/nterm/vault/credential_manager.py +163 -0
- ntermqt-0.1.0/nterm/vault/keychain.py +135 -0
- ntermqt-0.1.0/nterm/vault/manager_ui.py +962 -0
- ntermqt-0.1.0/nterm/vault/profile.py +219 -0
- ntermqt-0.1.0/nterm/vault/resolver.py +250 -0
- ntermqt-0.1.0/nterm/vault/store.py +642 -0
- ntermqt-0.1.0/ntermqt.egg-info/PKG-INFO +327 -0
- ntermqt-0.1.0/ntermqt.egg-info/SOURCES.txt +56 -0
- ntermqt-0.1.0/ntermqt.egg-info/dependency_links.txt +1 -0
- ntermqt-0.1.0/ntermqt.egg-info/entry_points.txt +5 -0
- ntermqt-0.1.0/ntermqt.egg-info/requires.txt +15 -0
- ntermqt-0.1.0/ntermqt.egg-info/top_level.txt +1 -0
- ntermqt-0.1.0/pyproject.toml +59 -0
- ntermqt-0.1.0/setup.cfg +4 -0
ntermqt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ntermqt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern SSH terminal widget for PyQt6 with credential vault and jump host support
|
|
5
|
+
Author: Scott Peterman
|
|
6
|
+
License: GPL-3.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/scottpeterman/nterm
|
|
8
|
+
Project-URL: Repository, https://github.com/scottpeterman/nterm
|
|
9
|
+
Project-URL: Issues, https://github.com/scottpeterman/nterm/issues
|
|
10
|
+
Keywords: ssh,terminal,pyqt6,network,automation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: System :: Networking
|
|
20
|
+
Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
24
|
+
Requires-Dist: PyQt6-WebEngine>=6.4.0
|
|
25
|
+
Requires-Dist: paramiko>=3.0.0
|
|
26
|
+
Requires-Dist: cryptography>=41.0.0
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Provides-Extra: keyring
|
|
29
|
+
Requires-Dist: keyring>=24.0.0; extra == "keyring"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Requires-Dist: black; extra == "dev"
|
|
33
|
+
Requires-Dist: pyinstaller; extra == "dev"
|
|
34
|
+
Requires-Dist: build; extra == "dev"
|
|
35
|
+
Requires-Dist: twine; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# nterm
|
|
38
|
+
|
|
39
|
+
**A modern SSH terminal for network engineers**
|
|
40
|
+
|
|
41
|
+
PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiKey/FIDO2 support, and legacy device compatibility.
|
|
42
|
+
|
|
43
|
+
Built for managing hundreds of devices through bastion hosts with hardware security keys.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
**Terminal**
|
|
52
|
+
- xterm.js rendering via QWebEngineView — full VT100/ANSI support
|
|
53
|
+
- Built-in themes: Catppuccin, Dracula, Nord, Solarized, Gruvbox (dark/light/hybrid)
|
|
54
|
+
- Custom YAML themes with independent terminal and UI colors
|
|
55
|
+
- Tab or window per session — pop sessions to separate windows
|
|
56
|
+
- Unicode, emoji, box-drawing characters
|
|
57
|
+
|
|
58
|
+
**Authentication**
|
|
59
|
+
- SSH Agent with YubiKey/FIDO2 hardware keys
|
|
60
|
+
- Password, key file, keyboard-interactive, certificate auth
|
|
61
|
+
- Multiple auth methods with automatic fallback
|
|
62
|
+
- RSA SHA-1 fallback for legacy devices (OpenSSH < 7.2)
|
|
63
|
+
- Legacy crypto support for old Juniper/Cisco gear
|
|
64
|
+
|
|
65
|
+
**Connection Management**
|
|
66
|
+
- Jump host chaining (unlimited hops)
|
|
67
|
+
- Auto-reconnection with exponential backoff
|
|
68
|
+
- Connection profiles in YAML/JSON
|
|
69
|
+
- Pattern-based credential resolution
|
|
70
|
+
|
|
71
|
+
**Credential Vault**
|
|
72
|
+
- AES-256 encryption with PBKDF2 (480,000 iterations)
|
|
73
|
+
- Pattern matching — map credentials to hosts by wildcard or tag
|
|
74
|
+
- Cross-platform keychain: macOS Keychain, Windows Credential Locker, Linux Secret Service
|
|
75
|
+
- Full PyQt6 management UI
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Screenshots
|
|
80
|
+
|
|
81
|
+
| Gruvbox Hybrid Theme | Credential Manager |
|
|
82
|
+
|---------------------|-------------------|
|
|
83
|
+
|  |  |
|
|
84
|
+
|
|
85
|
+
| Connection Dialog | Multi-vendor Support |
|
|
86
|
+
|----------------------------------------------------------------------------------------------|---------------------|
|
|
87
|
+
|  |  |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
### From PyPI
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install nterm
|
|
97
|
+
|
|
98
|
+
# Optional: system keychain support
|
|
99
|
+
pip install nterm[keyring]
|
|
100
|
+
|
|
101
|
+
# Run
|
|
102
|
+
nterm
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### From Source
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/scottpeterman/nterm.git
|
|
109
|
+
cd nterm
|
|
110
|
+
|
|
111
|
+
# Create virtual environment
|
|
112
|
+
python -m venv .venv
|
|
113
|
+
source .venv/bin/activate # Linux/macOS
|
|
114
|
+
# .venv\Scripts\activate # Windows
|
|
115
|
+
|
|
116
|
+
# Install in development mode
|
|
117
|
+
pip install -e .
|
|
118
|
+
|
|
119
|
+
# Optional: system keychain support
|
|
120
|
+
pip install keyring
|
|
121
|
+
|
|
122
|
+
# Run
|
|
123
|
+
nterm
|
|
124
|
+
# or
|
|
125
|
+
python -m nterm
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Requirements
|
|
129
|
+
|
|
130
|
+
- Python 3.10+
|
|
131
|
+
- PyQt6 with WebEngine
|
|
132
|
+
- paramiko
|
|
133
|
+
- cryptography
|
|
134
|
+
- pyyaml
|
|
135
|
+
|
|
136
|
+
### Platform Support
|
|
137
|
+
|
|
138
|
+
| Platform | PTY | Keychain |
|
|
139
|
+
|----------|-----|----------|
|
|
140
|
+
| Linux | ✅ Native | Secret Service |
|
|
141
|
+
| macOS | ✅ Native | macOS Keychain |
|
|
142
|
+
| Windows 10+ | ✅ pywinpty | Credential Locker |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Quick Start
|
|
147
|
+
|
|
148
|
+
### As a Widget
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from PyQt6.QtWidgets import QApplication, QMainWindow
|
|
152
|
+
from nterm import ConnectionProfile, AuthConfig, SSHSession, TerminalWidget, Theme
|
|
153
|
+
|
|
154
|
+
app = QApplication([])
|
|
155
|
+
|
|
156
|
+
terminal = TerminalWidget()
|
|
157
|
+
terminal.set_theme(Theme.gruvbox_hybrid())
|
|
158
|
+
|
|
159
|
+
profile = ConnectionProfile(
|
|
160
|
+
name="router",
|
|
161
|
+
hostname="192.168.1.1",
|
|
162
|
+
auth_methods=[AuthConfig.password_auth("admin", "secret")],
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
session = SSHSession(profile)
|
|
166
|
+
terminal.attach_session(session)
|
|
167
|
+
session.connect()
|
|
168
|
+
|
|
169
|
+
window = QMainWindow()
|
|
170
|
+
window.setCentralWidget(terminal)
|
|
171
|
+
window.resize(1000, 700)
|
|
172
|
+
window.show()
|
|
173
|
+
|
|
174
|
+
app.exec()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### With Credential Vault
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from nterm.vault import CredentialStore, CredentialResolver
|
|
181
|
+
|
|
182
|
+
store = CredentialStore()
|
|
183
|
+
store.unlock("master-password")
|
|
184
|
+
|
|
185
|
+
# Add credential with pattern matching
|
|
186
|
+
store.add_credential(
|
|
187
|
+
name="network-devices",
|
|
188
|
+
username="admin",
|
|
189
|
+
password="secret",
|
|
190
|
+
match_hosts=["*.network.corp", "192.168.1.*"],
|
|
191
|
+
match_tags=["cisco", "juniper"],
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Auto-resolve credentials by hostname
|
|
195
|
+
resolver = CredentialResolver(store)
|
|
196
|
+
profile = resolver.resolve_for_device("switch01.network.corp", tags=["cisco"])
|
|
197
|
+
|
|
198
|
+
session = SSHSession(profile)
|
|
199
|
+
session.connect()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Themes
|
|
205
|
+
|
|
206
|
+
### Built-in
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
Theme.default() # Catppuccin Mocha
|
|
210
|
+
Theme.dracula() # Dracula
|
|
211
|
+
Theme.nord() # Nord
|
|
212
|
+
Theme.solarized_dark() # Solarized Dark
|
|
213
|
+
Theme.gruvbox_dark() # Gruvbox Dark
|
|
214
|
+
Theme.gruvbox_light() # Gruvbox Light
|
|
215
|
+
Theme.gruvbox_hybrid() # Dark UI + Light terminal
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Custom YAML
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
# ~/.nterm/themes/my-theme.yaml
|
|
222
|
+
name: my-theme
|
|
223
|
+
|
|
224
|
+
terminal_colors:
|
|
225
|
+
background: "#1a1b26"
|
|
226
|
+
foreground: "#c0caf5"
|
|
227
|
+
cursor: "#c0caf5"
|
|
228
|
+
black: "#15161e"
|
|
229
|
+
red: "#f7768e"
|
|
230
|
+
green: "#9ece6a"
|
|
231
|
+
yellow: "#e0af68"
|
|
232
|
+
blue: "#7aa2f7"
|
|
233
|
+
magenta: "#bb9af7"
|
|
234
|
+
cyan: "#7dcfff"
|
|
235
|
+
white: "#a9b1d6"
|
|
236
|
+
# ... bright variants
|
|
237
|
+
|
|
238
|
+
# UI chrome (can differ from terminal)
|
|
239
|
+
background_color: "#1a1b26"
|
|
240
|
+
foreground_color: "#c0caf5"
|
|
241
|
+
border_color: "#33467c"
|
|
242
|
+
accent_color: "#7aa2f7"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Jump Hosts
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
profile = ConnectionProfile(
|
|
251
|
+
name="internal-db",
|
|
252
|
+
hostname="db01.internal.corp",
|
|
253
|
+
auth_methods=[AuthConfig.agent_auth("dbadmin")],
|
|
254
|
+
jump_hosts=[
|
|
255
|
+
JumpHostConfig(
|
|
256
|
+
hostname="bastion.corp.com",
|
|
257
|
+
auth=AuthConfig.agent_auth("youruser"),
|
|
258
|
+
requires_touch=True,
|
|
259
|
+
touch_prompt="Touch YubiKey for bastion...",
|
|
260
|
+
),
|
|
261
|
+
],
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Legacy Device Support
|
|
268
|
+
|
|
269
|
+
nterm automatically handles old network equipment:
|
|
270
|
+
|
|
271
|
+
- **RSA SHA-1 fallback** for OpenSSH < 7.2 servers
|
|
272
|
+
- **Legacy KEX algorithms**: diffie-hellman-group14-sha1, group1-sha1
|
|
273
|
+
- **Legacy ciphers**: aes128-cbc, 3des-cbc
|
|
274
|
+
- **Auto-detection**: tries modern crypto first, falls back as needed
|
|
275
|
+
|
|
276
|
+
Tested with:
|
|
277
|
+
- Junos 14.x (2016)
|
|
278
|
+
- Cisco IOS 12.2
|
|
279
|
+
- Old Arista EOS
|
|
280
|
+
- Any device running OpenSSH 6.x
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Architecture
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
nterm/
|
|
288
|
+
├── connection/ # ConnectionProfile, AuthConfig, JumpHostConfig
|
|
289
|
+
├── session/
|
|
290
|
+
│ ├── ssh.py # SSHSession (Paramiko) with legacy fallback
|
|
291
|
+
│ ├── interactive_ssh.py # Native SSH + PTY
|
|
292
|
+
│ └── pty_transport.py # Cross-platform PTY
|
|
293
|
+
├── terminal/
|
|
294
|
+
│ ├── widget.py # TerminalWidget (PyQt6 + xterm.js)
|
|
295
|
+
│ └── bridge.py # Qt ↔ JavaScript bridge
|
|
296
|
+
├── theme/
|
|
297
|
+
│ ├── engine.py # Theme system
|
|
298
|
+
│ └── themes/ # YAML theme files
|
|
299
|
+
├── vault/
|
|
300
|
+
│ ├── store.py # Encrypted credential storage
|
|
301
|
+
│ ├── resolver.py # Pattern-based resolution
|
|
302
|
+
│ └── manager_ui.py # PyQt6 credential manager
|
|
303
|
+
└── manager/ # Session tree, connection dialogs
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Related Projects
|
|
309
|
+
|
|
310
|
+
- [TerminalTelemetry](https://github.com/scottpeterman/terminaltelemetry) — PyQt6 terminal with network monitoring
|
|
311
|
+
- [Secure Cartography](https://github.com/scottpeterman/secure_cartography) — Network discovery and mapping
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
GPLv3
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Contributing
|
|
322
|
+
|
|
323
|
+
Contributions welcome:
|
|
324
|
+
- Additional themes
|
|
325
|
+
- Windows testing
|
|
326
|
+
- Session recording/playback
|
|
327
|
+
- Telnet/serial support
|
ntermqt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# nterm
|
|
2
|
+
|
|
3
|
+
**A modern SSH terminal for network engineers**
|
|
4
|
+
|
|
5
|
+
PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiKey/FIDO2 support, and legacy device compatibility.
|
|
6
|
+
|
|
7
|
+
Built for managing hundreds of devices through bastion hosts with hardware security keys.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
**Terminal**
|
|
16
|
+
- xterm.js rendering via QWebEngineView — full VT100/ANSI support
|
|
17
|
+
- Built-in themes: Catppuccin, Dracula, Nord, Solarized, Gruvbox (dark/light/hybrid)
|
|
18
|
+
- Custom YAML themes with independent terminal and UI colors
|
|
19
|
+
- Tab or window per session — pop sessions to separate windows
|
|
20
|
+
- Unicode, emoji, box-drawing characters
|
|
21
|
+
|
|
22
|
+
**Authentication**
|
|
23
|
+
- SSH Agent with YubiKey/FIDO2 hardware keys
|
|
24
|
+
- Password, key file, keyboard-interactive, certificate auth
|
|
25
|
+
- Multiple auth methods with automatic fallback
|
|
26
|
+
- RSA SHA-1 fallback for legacy devices (OpenSSH < 7.2)
|
|
27
|
+
- Legacy crypto support for old Juniper/Cisco gear
|
|
28
|
+
|
|
29
|
+
**Connection Management**
|
|
30
|
+
- Jump host chaining (unlimited hops)
|
|
31
|
+
- Auto-reconnection with exponential backoff
|
|
32
|
+
- Connection profiles in YAML/JSON
|
|
33
|
+
- Pattern-based credential resolution
|
|
34
|
+
|
|
35
|
+
**Credential Vault**
|
|
36
|
+
- AES-256 encryption with PBKDF2 (480,000 iterations)
|
|
37
|
+
- Pattern matching — map credentials to hosts by wildcard or tag
|
|
38
|
+
- Cross-platform keychain: macOS Keychain, Windows Credential Locker, Linux Secret Service
|
|
39
|
+
- Full PyQt6 management UI
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Screenshots
|
|
44
|
+
|
|
45
|
+
| Gruvbox Hybrid Theme | Credential Manager |
|
|
46
|
+
|---------------------|-------------------|
|
|
47
|
+
|  |  |
|
|
48
|
+
|
|
49
|
+
| Connection Dialog | Multi-vendor Support |
|
|
50
|
+
|----------------------------------------------------------------------------------------------|---------------------|
|
|
51
|
+
|  |  |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### From PyPI
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install nterm
|
|
61
|
+
|
|
62
|
+
# Optional: system keychain support
|
|
63
|
+
pip install nterm[keyring]
|
|
64
|
+
|
|
65
|
+
# Run
|
|
66
|
+
nterm
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### From Source
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/scottpeterman/nterm.git
|
|
73
|
+
cd nterm
|
|
74
|
+
|
|
75
|
+
# Create virtual environment
|
|
76
|
+
python -m venv .venv
|
|
77
|
+
source .venv/bin/activate # Linux/macOS
|
|
78
|
+
# .venv\Scripts\activate # Windows
|
|
79
|
+
|
|
80
|
+
# Install in development mode
|
|
81
|
+
pip install -e .
|
|
82
|
+
|
|
83
|
+
# Optional: system keychain support
|
|
84
|
+
pip install keyring
|
|
85
|
+
|
|
86
|
+
# Run
|
|
87
|
+
nterm
|
|
88
|
+
# or
|
|
89
|
+
python -m nterm
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Requirements
|
|
93
|
+
|
|
94
|
+
- Python 3.10+
|
|
95
|
+
- PyQt6 with WebEngine
|
|
96
|
+
- paramiko
|
|
97
|
+
- cryptography
|
|
98
|
+
- pyyaml
|
|
99
|
+
|
|
100
|
+
### Platform Support
|
|
101
|
+
|
|
102
|
+
| Platform | PTY | Keychain |
|
|
103
|
+
|----------|-----|----------|
|
|
104
|
+
| Linux | ✅ Native | Secret Service |
|
|
105
|
+
| macOS | ✅ Native | macOS Keychain |
|
|
106
|
+
| Windows 10+ | ✅ pywinpty | Credential Locker |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Quick Start
|
|
111
|
+
|
|
112
|
+
### As a Widget
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from PyQt6.QtWidgets import QApplication, QMainWindow
|
|
116
|
+
from nterm import ConnectionProfile, AuthConfig, SSHSession, TerminalWidget, Theme
|
|
117
|
+
|
|
118
|
+
app = QApplication([])
|
|
119
|
+
|
|
120
|
+
terminal = TerminalWidget()
|
|
121
|
+
terminal.set_theme(Theme.gruvbox_hybrid())
|
|
122
|
+
|
|
123
|
+
profile = ConnectionProfile(
|
|
124
|
+
name="router",
|
|
125
|
+
hostname="192.168.1.1",
|
|
126
|
+
auth_methods=[AuthConfig.password_auth("admin", "secret")],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
session = SSHSession(profile)
|
|
130
|
+
terminal.attach_session(session)
|
|
131
|
+
session.connect()
|
|
132
|
+
|
|
133
|
+
window = QMainWindow()
|
|
134
|
+
window.setCentralWidget(terminal)
|
|
135
|
+
window.resize(1000, 700)
|
|
136
|
+
window.show()
|
|
137
|
+
|
|
138
|
+
app.exec()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### With Credential Vault
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from nterm.vault import CredentialStore, CredentialResolver
|
|
145
|
+
|
|
146
|
+
store = CredentialStore()
|
|
147
|
+
store.unlock("master-password")
|
|
148
|
+
|
|
149
|
+
# Add credential with pattern matching
|
|
150
|
+
store.add_credential(
|
|
151
|
+
name="network-devices",
|
|
152
|
+
username="admin",
|
|
153
|
+
password="secret",
|
|
154
|
+
match_hosts=["*.network.corp", "192.168.1.*"],
|
|
155
|
+
match_tags=["cisco", "juniper"],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Auto-resolve credentials by hostname
|
|
159
|
+
resolver = CredentialResolver(store)
|
|
160
|
+
profile = resolver.resolve_for_device("switch01.network.corp", tags=["cisco"])
|
|
161
|
+
|
|
162
|
+
session = SSHSession(profile)
|
|
163
|
+
session.connect()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Themes
|
|
169
|
+
|
|
170
|
+
### Built-in
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
Theme.default() # Catppuccin Mocha
|
|
174
|
+
Theme.dracula() # Dracula
|
|
175
|
+
Theme.nord() # Nord
|
|
176
|
+
Theme.solarized_dark() # Solarized Dark
|
|
177
|
+
Theme.gruvbox_dark() # Gruvbox Dark
|
|
178
|
+
Theme.gruvbox_light() # Gruvbox Light
|
|
179
|
+
Theme.gruvbox_hybrid() # Dark UI + Light terminal
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Custom YAML
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
# ~/.nterm/themes/my-theme.yaml
|
|
186
|
+
name: my-theme
|
|
187
|
+
|
|
188
|
+
terminal_colors:
|
|
189
|
+
background: "#1a1b26"
|
|
190
|
+
foreground: "#c0caf5"
|
|
191
|
+
cursor: "#c0caf5"
|
|
192
|
+
black: "#15161e"
|
|
193
|
+
red: "#f7768e"
|
|
194
|
+
green: "#9ece6a"
|
|
195
|
+
yellow: "#e0af68"
|
|
196
|
+
blue: "#7aa2f7"
|
|
197
|
+
magenta: "#bb9af7"
|
|
198
|
+
cyan: "#7dcfff"
|
|
199
|
+
white: "#a9b1d6"
|
|
200
|
+
# ... bright variants
|
|
201
|
+
|
|
202
|
+
# UI chrome (can differ from terminal)
|
|
203
|
+
background_color: "#1a1b26"
|
|
204
|
+
foreground_color: "#c0caf5"
|
|
205
|
+
border_color: "#33467c"
|
|
206
|
+
accent_color: "#7aa2f7"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Jump Hosts
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
profile = ConnectionProfile(
|
|
215
|
+
name="internal-db",
|
|
216
|
+
hostname="db01.internal.corp",
|
|
217
|
+
auth_methods=[AuthConfig.agent_auth("dbadmin")],
|
|
218
|
+
jump_hosts=[
|
|
219
|
+
JumpHostConfig(
|
|
220
|
+
hostname="bastion.corp.com",
|
|
221
|
+
auth=AuthConfig.agent_auth("youruser"),
|
|
222
|
+
requires_touch=True,
|
|
223
|
+
touch_prompt="Touch YubiKey for bastion...",
|
|
224
|
+
),
|
|
225
|
+
],
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Legacy Device Support
|
|
232
|
+
|
|
233
|
+
nterm automatically handles old network equipment:
|
|
234
|
+
|
|
235
|
+
- **RSA SHA-1 fallback** for OpenSSH < 7.2 servers
|
|
236
|
+
- **Legacy KEX algorithms**: diffie-hellman-group14-sha1, group1-sha1
|
|
237
|
+
- **Legacy ciphers**: aes128-cbc, 3des-cbc
|
|
238
|
+
- **Auto-detection**: tries modern crypto first, falls back as needed
|
|
239
|
+
|
|
240
|
+
Tested with:
|
|
241
|
+
- Junos 14.x (2016)
|
|
242
|
+
- Cisco IOS 12.2
|
|
243
|
+
- Old Arista EOS
|
|
244
|
+
- Any device running OpenSSH 6.x
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Architecture
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
nterm/
|
|
252
|
+
├── connection/ # ConnectionProfile, AuthConfig, JumpHostConfig
|
|
253
|
+
├── session/
|
|
254
|
+
│ ├── ssh.py # SSHSession (Paramiko) with legacy fallback
|
|
255
|
+
│ ├── interactive_ssh.py # Native SSH + PTY
|
|
256
|
+
│ └── pty_transport.py # Cross-platform PTY
|
|
257
|
+
├── terminal/
|
|
258
|
+
│ ├── widget.py # TerminalWidget (PyQt6 + xterm.js)
|
|
259
|
+
│ └── bridge.py # Qt ↔ JavaScript bridge
|
|
260
|
+
├── theme/
|
|
261
|
+
│ ├── engine.py # Theme system
|
|
262
|
+
│ └── themes/ # YAML theme files
|
|
263
|
+
├── vault/
|
|
264
|
+
│ ├── store.py # Encrypted credential storage
|
|
265
|
+
│ ├── resolver.py # Pattern-based resolution
|
|
266
|
+
│ └── manager_ui.py # PyQt6 credential manager
|
|
267
|
+
└── manager/ # Session tree, connection dialogs
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Related Projects
|
|
273
|
+
|
|
274
|
+
- [TerminalTelemetry](https://github.com/scottpeterman/terminaltelemetry) — PyQt6 terminal with network monitoring
|
|
275
|
+
- [Secure Cartography](https://github.com/scottpeterman/secure_cartography) — Network discovery and mapping
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
GPLv3
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Contributing
|
|
286
|
+
|
|
287
|
+
Contributions welcome:
|
|
288
|
+
- Additional themes
|
|
289
|
+
- Windows testing
|
|
290
|
+
- Session recording/playback
|
|
291
|
+
- Telnet/serial support
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
nterm - A themeable SSH terminal widget for PyQt6.
|
|
3
|
+
|
|
4
|
+
Clean architecture with:
|
|
5
|
+
- Connection profiles (fully serializable)
|
|
6
|
+
- Session management with auto-reconnect
|
|
7
|
+
- Jump host / bastion support
|
|
8
|
+
- YubiKey/FIDO2 agent auth (native SSH + PTY)
|
|
9
|
+
- xterm.js rendering
|
|
10
|
+
- Themeable UI
|
|
11
|
+
|
|
12
|
+
Session types:
|
|
13
|
+
- SSHSession: Paramiko-based, programmatic auth
|
|
14
|
+
- InteractiveSSHSession: Native ssh with PTY for full interactive auth
|
|
15
|
+
- HybridSSHSession: Interactive auth with ControlMaster reuse (Unix only)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__version__ = "0.2.0"
|
|
19
|
+
__author__ = "Scott Peterman"
|
|
20
|
+
|
|
21
|
+
from .connection.profile import (
|
|
22
|
+
ConnectionProfile,
|
|
23
|
+
AuthConfig,
|
|
24
|
+
AuthMethod,
|
|
25
|
+
JumpHostConfig,
|
|
26
|
+
)
|
|
27
|
+
from .session.base import Session, SessionState
|
|
28
|
+
from .session.ssh import SSHSession
|
|
29
|
+
from .session.interactive_ssh import InteractiveSSHSession, HybridSSHSession
|
|
30
|
+
from .session.pty_transport import is_pty_available, IS_WINDOWS
|
|
31
|
+
from .terminal.widget import TerminalWidget
|
|
32
|
+
from .theme.engine import Theme, ThemeEngine
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Connection
|
|
36
|
+
"ConnectionProfile",
|
|
37
|
+
"AuthConfig",
|
|
38
|
+
"AuthMethod",
|
|
39
|
+
"JumpHostConfig",
|
|
40
|
+
# Sessions
|
|
41
|
+
"Session",
|
|
42
|
+
"SessionState",
|
|
43
|
+
"SSHSession",
|
|
44
|
+
"InteractiveSSHSession",
|
|
45
|
+
"HybridSSHSession",
|
|
46
|
+
# Utilities
|
|
47
|
+
"is_pty_available",
|
|
48
|
+
"IS_WINDOWS",
|
|
49
|
+
# Terminal
|
|
50
|
+
"TerminalWidget",
|
|
51
|
+
# Themes
|
|
52
|
+
"Theme",
|
|
53
|
+
"ThemeEngine",
|
|
54
|
+
]
|