ntermqt 0.1.0__tar.gz → 0.1.1__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.
Files changed (61) hide show
  1. {ntermqt-0.1.0/ntermqt.egg-info → ntermqt-0.1.1}/PKG-INFO +2 -2
  2. {ntermqt-0.1.0 → ntermqt-0.1.1}/README.md +1 -1
  3. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/__main__.py +213 -10
  4. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/engine.py +238 -0
  5. ntermqt-0.1.1/nterm/theme/themes/enterprise_dark.yaml +42 -0
  6. ntermqt-0.1.1/nterm/theme/themes/enterprise_hybrid.yaml +44 -0
  7. ntermqt-0.1.1/nterm/theme/themes/enterprise_light.yaml +42 -0
  8. {ntermqt-0.1.0 → ntermqt-0.1.1/ntermqt.egg-info}/PKG-INFO +2 -2
  9. {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/SOURCES.txt +3 -0
  10. {ntermqt-0.1.0 → ntermqt-0.1.1}/pyproject.toml +1 -1
  11. {ntermqt-0.1.0 → ntermqt-0.1.1}/MANIFEST.in +0 -0
  12. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/__init__.py +0 -0
  13. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/askpass/__init__.py +0 -0
  14. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/askpass/server.py +0 -0
  15. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/config.py +0 -0
  16. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/connection/__init__.py +0 -0
  17. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/connection/profile.py +0 -0
  18. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/__init__.py +0 -0
  19. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/connect_dialog.py +0 -0
  20. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/editor.py +0 -0
  21. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/io.py +0 -0
  22. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/models.py +0 -0
  23. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/settings.py +0 -0
  24. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/tree.py +0 -0
  25. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/resources.py +0 -0
  26. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/__init__.py +0 -0
  27. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/askpass_ssh.py +0 -0
  28. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/base.py +0 -0
  29. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/interactive_ssh.py +0 -0
  30. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/pty_transport.py +0 -0
  31. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/ssh.py +0 -0
  32. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/__init__.py +0 -0
  33. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/bridge.py +0 -0
  34. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/terminal.html +0 -0
  35. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/terminal.js +0 -0
  36. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm-addon-fit.min.js +0 -0
  37. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm-addon-unicode11.min.js +0 -0
  38. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm-addon-web-links.min.js +0 -0
  39. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm.css +0 -0
  40. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm.min.js +0 -0
  41. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/widget.py +0 -0
  42. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/__init__.py +0 -0
  43. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/stylesheet.py +0 -0
  44. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/clean.yaml +0 -0
  45. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/default.yaml +0 -0
  46. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/dracula.yaml +0 -0
  47. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/gruvbox_dark.yaml +0 -0
  48. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/gruvbox_hybrid.yaml +0 -0
  49. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/gruvbox_light.yaml +0 -0
  50. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/__init__.py +0 -0
  51. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/credential_manager.py +0 -0
  52. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/keychain.py +0 -0
  53. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/manager_ui.py +0 -0
  54. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/profile.py +0 -0
  55. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/resolver.py +0 -0
  56. {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/store.py +0 -0
  57. {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/dependency_links.txt +0 -0
  58. {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/entry_points.txt +0 -0
  59. {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/requires.txt +0 -0
  60. {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/top_level.txt +0 -0
  61. {ntermqt-0.1.0 → ntermqt-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ntermqt
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Modern SSH terminal widget for PyQt6 with credential vault and jump host support
5
5
  Author: Scott Peterman
6
6
  License: GPL-3.0
@@ -42,7 +42,7 @@ PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiK
42
42
 
43
43
  Built for managing hundreds of devices through bastion hosts with hardware security keys.
44
44
 
45
- ![nterm screenshot](https://raw.githubusercontent.com/scottpeterman/nterm/main/screenshots/gruvbox.png)
45
+ ![nterm screenshot](https://raw.githubusercontent.com/scottpeterman/nterm/main/screenshots/slides.gif)
46
46
 
47
47
  ---
48
48
 
@@ -6,7 +6,7 @@ PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiK
6
6
 
7
7
  Built for managing hundreds of devices through bastion hosts with hardware security keys.
8
8
 
9
- ![nterm screenshot](https://raw.githubusercontent.com/scottpeterman/nterm/main/screenshots/gruvbox.png)
9
+ ![nterm screenshot](https://raw.githubusercontent.com/scottpeterman/nterm/main/screenshots/slides.gif)
10
10
 
11
11
  ---
12
12
 
@@ -11,7 +11,7 @@ from PyQt6.QtWidgets import (
11
11
  QApplication, QMainWindow, QSplitter, QTabWidget,
12
12
  QWidget, QVBoxLayout, QHBoxLayout, QMessageBox,
13
13
  QDialog, QLabel, QLineEdit, QPushButton, QCheckBox,
14
- QMenuBar, QMenu
14
+ QMenuBar, QMenu, QTabBar
15
15
  )
16
16
  from PyQt6.QtCore import Qt
17
17
  from PyQt6.QtGui import QAction, QKeySequence
@@ -207,6 +207,23 @@ class TerminalTab(QWidget):
207
207
  """Disconnect the session."""
208
208
  self.ssh_session.disconnect()
209
209
 
210
+ def is_connected(self) -> bool:
211
+ """Check if the session is currently connected."""
212
+ # Try multiple ways to detect connection state
213
+ if hasattr(self.ssh_session, 'is_connected'):
214
+ return self.ssh_session.is_connected
215
+ if hasattr(self.ssh_session, 'connected'):
216
+ return self.ssh_session.connected
217
+ if hasattr(self.ssh_session, '_connected'):
218
+ return self.ssh_session._connected
219
+ if hasattr(self.ssh_session, '_channel'):
220
+ return self.ssh_session._channel is not None
221
+ if hasattr(self.ssh_session, '_transport'):
222
+ transport = self.ssh_session._transport
223
+ return transport is not None and transport.is_active()
224
+ # Default to True if we can't determine - safer to warn
225
+ return True
226
+
210
227
 
211
228
  class MainWindow(QMainWindow):
212
229
  """
@@ -249,12 +266,6 @@ class MainWindow(QMainWindow):
249
266
 
250
267
  # Apply initial stylesheet
251
268
  self._apply_qt_theme(self.current_theme)
252
- self._setup_ui()
253
- self._connect_signals()
254
- self._refresh_credentials()
255
-
256
- # Apply initial stylesheet
257
- self._apply_qt_theme(self.current_theme)
258
269
 
259
270
  def _on_settings_changed(self, settings):
260
271
  """Handle settings changes from dialog."""
@@ -298,6 +309,10 @@ class MainWindow(QMainWindow):
298
309
  self.tab_widget.setDocumentMode(True)
299
310
  splitter.addWidget(self.tab_widget)
300
311
 
312
+ # Enable tab context menu
313
+ self.tab_widget.tabBar().setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
314
+ self.tab_widget.tabBar().customContextMenuRequested.connect(self._show_tab_context_menu)
315
+
301
316
  # Set initial sizes (tree: 250px, tabs: rest)
302
317
  splitter.setSizes([250, 950])
303
318
 
@@ -323,6 +338,20 @@ class MainWindow(QMainWindow):
323
338
 
324
339
  file_menu.addSeparator()
325
340
 
341
+ # --- NEW: Tab management actions ---
342
+ close_tab_action = QAction("Close Tab", self)
343
+ close_tab_action.setShortcut(QKeySequence("Ctrl+W"))
344
+ close_tab_action.triggered.connect(self._close_current_tab)
345
+ file_menu.addAction(close_tab_action)
346
+
347
+ close_all_action = QAction("Close All Tabs", self)
348
+ close_all_action.setShortcut(QKeySequence("Ctrl+Shift+W"))
349
+ close_all_action.triggered.connect(self._close_all_tabs)
350
+ file_menu.addAction(close_all_action)
351
+
352
+ file_menu.addSeparator()
353
+ # --- END NEW ---
354
+
326
355
  import_action = QAction("&Import Sessions...", self)
327
356
  import_action.setShortcut(QKeySequence("Ctrl+I"))
328
357
  import_action.triggered.connect(self._on_import_sessions)
@@ -531,15 +560,159 @@ class MainWindow(QMainWindow):
531
560
  self._child_windows.append(window)
532
561
  window.destroyed.connect(lambda: self._child_windows.remove(window))
533
562
 
563
+ # -------------------------------------------------------------------------
564
+ # Tab Management (NEW)
565
+ # -------------------------------------------------------------------------
566
+
567
+ def _get_active_session_count(self) -> int:
568
+ """Count tabs with active connections."""
569
+ count = 0
570
+ for i in range(self.tab_widget.count()):
571
+ tab = self.tab_widget.widget(i)
572
+ if isinstance(tab, TerminalTab) and tab.is_connected():
573
+ count += 1
574
+ return count
575
+
576
+ def _show_tab_context_menu(self, pos):
577
+ """Show context menu for tab bar."""
578
+ tab_bar = self.tab_widget.tabBar()
579
+ index = tab_bar.tabAt(pos)
580
+
581
+ menu = QMenu(self)
582
+
583
+ if index >= 0:
584
+ # Clicked on a tab
585
+ menu.addAction("Close", lambda: self._close_tab(index))
586
+ menu.addAction("Close Others", lambda: self._close_other_tabs(index))
587
+ menu.addAction("Close Tabs to the Right", lambda: self._close_tabs_to_right(index))
588
+ menu.addSeparator()
589
+
590
+ if self.tab_widget.count() > 0:
591
+ menu.addAction("Close All Tabs", self._close_all_tabs)
592
+
593
+ if menu.actions():
594
+ menu.exec(tab_bar.mapToGlobal(pos))
595
+
596
+ def _close_current_tab(self):
597
+ """Close the currently active tab."""
598
+ index = self.tab_widget.currentIndex()
599
+ if index >= 0:
600
+ self._close_tab(index)
601
+
534
602
  def _close_tab(self, index: int):
535
- """Close a terminal tab."""
603
+ """Close a terminal tab with confirmation if connected."""
536
604
  tab = self.tab_widget.widget(index)
605
+
537
606
  if isinstance(tab, TerminalTab):
607
+ # Check if session is active
608
+ if tab.is_connected():
609
+ reply = QMessageBox.question(
610
+ self,
611
+ "Close Tab",
612
+ f"'{tab.session.name}' has an active connection.\n\n"
613
+ "Disconnect and close this tab?",
614
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
615
+ QMessageBox.StandardButton.No
616
+ )
617
+ if reply != QMessageBox.StandardButton.Yes:
618
+ return
619
+
538
620
  tab.disconnect()
621
+
539
622
  self.tab_widget.removeTab(index)
540
623
 
624
+ def _close_other_tabs(self, keep_index: int):
625
+ """Close all tabs except the specified one."""
626
+ active_count = self._get_active_session_count()
627
+ tab_to_keep = self.tab_widget.widget(keep_index)
628
+ keep_is_active = isinstance(tab_to_keep, TerminalTab) and tab_to_keep.is_connected()
629
+ other_active = active_count - (1 if keep_is_active else 0)
630
+
631
+ if other_active > 0:
632
+ reply = QMessageBox.question(
633
+ self,
634
+ "Close Other Tabs",
635
+ f"{other_active} other tab(s) have active connections.\n\n"
636
+ "Disconnect and close them?",
637
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
638
+ QMessageBox.StandardButton.No
639
+ )
640
+ if reply != QMessageBox.StandardButton.Yes:
641
+ return
642
+
643
+ # Close tabs from end to avoid index shifting issues
644
+ for i in range(self.tab_widget.count() - 1, -1, -1):
645
+ if i != keep_index:
646
+ tab = self.tab_widget.widget(i)
647
+ if isinstance(tab, TerminalTab):
648
+ tab.disconnect()
649
+ self.tab_widget.removeTab(i)
650
+
651
+ def _close_tabs_to_right(self, index: int):
652
+ """Close all tabs to the right of the specified index."""
653
+ tabs_to_close = self.tab_widget.count() - index - 1
654
+ if tabs_to_close <= 0:
655
+ return
656
+
657
+ # Count active sessions to the right
658
+ active_count = 0
659
+ for i in range(index + 1, self.tab_widget.count()):
660
+ tab = self.tab_widget.widget(i)
661
+ if isinstance(tab, TerminalTab) and tab.is_connected():
662
+ active_count += 1
663
+
664
+ if active_count > 0:
665
+ reply = QMessageBox.question(
666
+ self,
667
+ "Close Tabs",
668
+ f"{active_count} tab(s) to the right have active connections.\n\n"
669
+ "Disconnect and close them?",
670
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
671
+ QMessageBox.StandardButton.No
672
+ )
673
+ if reply != QMessageBox.StandardButton.Yes:
674
+ return
675
+
676
+ # Close from end to avoid index shifting
677
+ for i in range(self.tab_widget.count() - 1, index, -1):
678
+ tab = self.tab_widget.widget(i)
679
+ if isinstance(tab, TerminalTab):
680
+ tab.disconnect()
681
+ self.tab_widget.removeTab(i)
682
+
683
+ def _close_all_tabs(self):
684
+ """Close all tabs with confirmation."""
685
+ if self.tab_widget.count() == 0:
686
+ return
687
+
688
+ active_count = self._get_active_session_count()
689
+
690
+ if active_count > 0:
691
+ reply = QMessageBox.question(
692
+ self,
693
+ "Close All Tabs",
694
+ f"{active_count} tab(s) have active connections.\n\n"
695
+ "Disconnect and close all tabs?",
696
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
697
+ QMessageBox.StandardButton.No
698
+ )
699
+ if reply != QMessageBox.StandardButton.Yes:
700
+ return
701
+
702
+ # Close all tabs
703
+ while self.tab_widget.count() > 0:
704
+ tab = self.tab_widget.widget(0)
705
+ if isinstance(tab, TerminalTab):
706
+ tab.disconnect()
707
+ self.tab_widget.removeTab(0)
708
+
709
+ # -------------------------------------------------------------------------
710
+ # End Tab Management
711
+ # -------------------------------------------------------------------------
712
+
541
713
  def closeEvent(self, event):
542
- """Clean up on close."""
714
+ """Clean up on close with confirmation for active sessions."""
715
+ # Save window geometry first
543
716
  if not self.isMaximized():
544
717
  self.app_settings.window_width = self.width()
545
718
  self.app_settings.window_height = self.height()
@@ -547,7 +720,24 @@ class MainWindow(QMainWindow):
547
720
  self.app_settings.window_y = self.y()
548
721
  self.app_settings.window_maximized = self.isMaximized()
549
722
 
723
+ # Check for active connections
724
+ active_count = self._get_active_session_count()
725
+
726
+ if active_count > 0:
727
+ reply = QMessageBox.question(
728
+ self,
729
+ "Quit nterm",
730
+ f"You have {active_count} active session(s).\n\n"
731
+ "Disconnect all and quit?",
732
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
733
+ QMessageBox.StandardButton.No
734
+ )
735
+ if reply != QMessageBox.StandardButton.Yes:
736
+ event.ignore()
737
+ return
738
+
550
739
  save_settings()
740
+
551
741
  # Disconnect all tabs
552
742
  for i in range(self.tab_widget.count()):
553
743
  tab = self.tab_widget.widget(i)
@@ -582,7 +772,20 @@ class TerminalWindow(QMainWindow):
582
772
  self.tab.connect()
583
773
 
584
774
  def closeEvent(self, event):
585
- """Disconnect on close."""
775
+ """Disconnect on close with confirmation."""
776
+ if self.tab.is_connected():
777
+ reply = QMessageBox.question(
778
+ self,
779
+ "Close Window",
780
+ f"'{self.tab.session.name}' has an active connection.\n\n"
781
+ "Disconnect and close?",
782
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
783
+ QMessageBox.StandardButton.No
784
+ )
785
+ if reply != QMessageBox.StandardButton.Yes:
786
+ event.ignore()
787
+ return
788
+
586
789
  self.tab.disconnect()
587
790
  event.accept()
588
791
 
@@ -285,6 +285,241 @@ class Theme:
285
285
  overlay_text_color="#3c3836",
286
286
  )
287
287
 
288
+
289
+ @classmethod
290
+ def enterprise_dark(cls) -> Theme:
291
+ """
292
+ Enterprise Dark theme.
293
+
294
+ Classic corporate dark theme with pure black terminal.
295
+ Microsoft-inspired blue accent palette.
296
+ """
297
+ return cls(
298
+ name="enterprise_dark",
299
+ terminal_colors={
300
+ "background": "#0c0c0c",
301
+ "foreground": "#cccccc",
302
+ "cursor": "#ffffff",
303
+ "cursorAccent": "#0c0c0c",
304
+ "selectionBackground": "#264f78",
305
+ "selectionForeground": "#ffffff",
306
+ # Windows Terminal-inspired palette
307
+ "black": "#0c0c0c",
308
+ "red": "#c50f1f",
309
+ "green": "#13a10e",
310
+ "yellow": "#c19c00",
311
+ "blue": "#0037da",
312
+ "magenta": "#881798",
313
+ "cyan": "#3a96dd",
314
+ "white": "#cccccc",
315
+ "brightBlack": "#767676",
316
+ "brightRed": "#e74856",
317
+ "brightGreen": "#16c60c",
318
+ "brightYellow": "#f9f1a5",
319
+ "brightBlue": "#3b78ff",
320
+ "brightMagenta": "#b4009e",
321
+ "brightCyan": "#61d6d6",
322
+ "brightWhite": "#f2f2f2",
323
+ },
324
+ font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
325
+ font_size=14,
326
+ background_color="#1e1e1e",
327
+ foreground_color="#d4d4d4",
328
+ border_color="#3c3c3c",
329
+ accent_color="#0078d4",
330
+ overlay_background="rgba(30, 30, 30, 0.95)",
331
+ overlay_text_color="#d4d4d4",
332
+ )
333
+
334
+ # Add these class methods to Theme class in engine.py
335
+ # Add to _themes dict in ThemeEngine.__init__():
336
+ # self._themes["enterprise_dark"] = Theme.enterprise_dark()
337
+ # self._themes["enterprise_light"] = Theme.enterprise_light()
338
+ # self._themes["enterprise_hybrid"] = Theme.enterprise_hybrid()
339
+
340
+ @classmethod
341
+ def enterprise_dark(cls) -> Theme:
342
+ """
343
+ Enterprise Dark theme.
344
+
345
+ Classic corporate dark theme with pure black terminal.
346
+ Microsoft-inspired blue accent palette.
347
+ """
348
+ return cls(
349
+ name="enterprise_dark",
350
+ terminal_colors={
351
+ "background": "#0c0c0c",
352
+ "foreground": "#cccccc",
353
+ "cursor": "#ffffff",
354
+ "cursorAccent": "#0c0c0c",
355
+ "selectionBackground": "#264f78",
356
+ "selectionForeground": "#ffffff",
357
+ # Windows Terminal-inspired palette
358
+ "black": "#0c0c0c",
359
+ "red": "#c50f1f",
360
+ "green": "#13a10e",
361
+ "yellow": "#c19c00",
362
+ "blue": "#0037da",
363
+ "magenta": "#881798",
364
+ "cyan": "#3a96dd",
365
+ "white": "#cccccc",
366
+ "brightBlack": "#767676",
367
+ "brightRed": "#e74856",
368
+ "brightGreen": "#16c60c",
369
+ "brightYellow": "#f9f1a5",
370
+ "brightBlue": "#3b78ff",
371
+ "brightMagenta": "#b4009e",
372
+ "brightCyan": "#61d6d6",
373
+ "brightWhite": "#f2f2f2",
374
+ },
375
+ font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
376
+ font_size=14,
377
+ background_color="#1e1e1e",
378
+ foreground_color="#d4d4d4",
379
+ border_color="#3c3c3c",
380
+ accent_color="#0078d4",
381
+ overlay_background="rgba(30, 30, 30, 0.95)",
382
+ overlay_text_color="#d4d4d4",
383
+ )
384
+
385
+ @classmethod
386
+ def enterprise_light(cls) -> Theme:
387
+ """
388
+ Enterprise Light theme.
389
+
390
+ Classic corporate light theme with white terminal.
391
+ Microsoft-inspired blue accent palette.
392
+ """
393
+ return cls(
394
+ name="enterprise_light",
395
+ terminal_colors={
396
+ "background": "#ffffff",
397
+ "foreground": "#1e1e1e",
398
+ "cursor": "#1e1e1e",
399
+ "cursorAccent": "#ffffff",
400
+ "selectionBackground": "#add6ff",
401
+ "selectionForeground": "#1e1e1e",
402
+ # Light terminal palette
403
+ "black": "#1e1e1e",
404
+ "red": "#c72e2e",
405
+ "green": "#098658",
406
+ "yellow": "#795e26",
407
+ "blue": "#0451a5",
408
+ "magenta": "#a626a4",
409
+ "cyan": "#0598bc",
410
+ "white": "#d4d4d4",
411
+ "brightBlack": "#5a5a5a",
412
+ "brightRed": "#e51400",
413
+ "brightGreen": "#14a114",
414
+ "brightYellow": "#b5a000",
415
+ "brightBlue": "#0078d4",
416
+ "brightMagenta": "#bc05bc",
417
+ "brightCyan": "#00b7c3",
418
+ "brightWhite": "#f8f8f8",
419
+ },
420
+ font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
421
+ font_size=14,
422
+ background_color="#f3f3f3",
423
+ foreground_color="#1e1e1e",
424
+ border_color="#d1d1d1",
425
+ accent_color="#0078d4",
426
+ overlay_background="rgba(243, 243, 243, 0.95)",
427
+ overlay_text_color="#1e1e1e",
428
+ )
429
+
430
+ @classmethod
431
+ def enterprise_hybrid(cls) -> Theme:
432
+ """
433
+ Enterprise Hybrid theme.
434
+
435
+ Dark UI chrome with white terminal.
436
+ Best of both: comfortable dark UI, high-contrast readable terminal.
437
+ Microsoft-inspired blue accent palette.
438
+ """
439
+ return cls(
440
+ name="enterprise_hybrid",
441
+ # Light terminal for readability
442
+ terminal_colors={
443
+ "background": "#ffffff",
444
+ "foreground": "#1e1e1e",
445
+ "cursor": "#1e1e1e",
446
+ "cursorAccent": "#ffffff",
447
+ "selectionBackground": "#add6ff",
448
+ "selectionForeground": "#1e1e1e",
449
+ "black": "#1e1e1e",
450
+ "red": "#c72e2e",
451
+ "green": "#098658",
452
+ "yellow": "#795e26",
453
+ "blue": "#0451a5",
454
+ "magenta": "#a626a4",
455
+ "cyan": "#0598bc",
456
+ "white": "#d4d4d4",
457
+ "brightBlack": "#5a5a5a",
458
+ "brightRed": "#e51400",
459
+ "brightGreen": "#14a114",
460
+ "brightYellow": "#b5a000",
461
+ "brightBlue": "#0078d4",
462
+ "brightMagenta": "#bc05bc",
463
+ "brightCyan": "#00b7c3",
464
+ "brightWhite": "#f8f8f8",
465
+ },
466
+ font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
467
+ font_size=14,
468
+ # Dark UI chrome
469
+ background_color="#1e1e1e",
470
+ foreground_color="#d4d4d4",
471
+ border_color="#3c3c3c",
472
+ accent_color="#0078d4",
473
+ overlay_background="rgba(30, 30, 30, 0.95)",
474
+ overlay_text_color="#d4d4d4",
475
+ )
476
+
477
+
478
+ @classmethod
479
+ def enterprise_light(cls) -> Theme:
480
+ """
481
+ Enterprise Light theme.
482
+
483
+ Classic corporate light theme with white terminal.
484
+ Microsoft-inspired blue accent palette.
485
+ """
486
+ return cls(
487
+ name="enterprise_light",
488
+ terminal_colors={
489
+ "background": "#ffffff",
490
+ "foreground": "#1e1e1e",
491
+ "cursor": "#1e1e1e",
492
+ "cursorAccent": "#ffffff",
493
+ "selectionBackground": "#add6ff",
494
+ "selectionForeground": "#1e1e1e",
495
+ # Light terminal palette
496
+ "black": "#1e1e1e",
497
+ "red": "#c72e2e",
498
+ "green": "#098658",
499
+ "yellow": "#795e26",
500
+ "blue": "#0451a5",
501
+ "magenta": "#a626a4",
502
+ "cyan": "#0598bc",
503
+ "white": "#d4d4d4",
504
+ "brightBlack": "#5a5a5a",
505
+ "brightRed": "#e51400",
506
+ "brightGreen": "#14a114",
507
+ "brightYellow": "#b5a000",
508
+ "brightBlue": "#0078d4",
509
+ "brightMagenta": "#bc05bc",
510
+ "brightCyan": "#00b7c3",
511
+ "brightWhite": "#f8f8f8",
512
+ },
513
+ font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
514
+ font_size=14,
515
+ background_color="#f3f3f3",
516
+ foreground_color="#1e1e1e",
517
+ border_color="#d1d1d1",
518
+ accent_color="#0078d4",
519
+ overlay_background="rgba(243, 243, 243, 0.95)",
520
+ overlay_text_color="#1e1e1e",
521
+ )
522
+
288
523
  @classmethod
289
524
  def clean(cls) -> Theme:
290
525
  """
@@ -400,6 +635,9 @@ class ThemeEngine:
400
635
  self._themes["gruvbox_light"] = Theme.gruvbox_light()
401
636
  self._themes["gruvbox_hybrid"] = Theme.gruvbox_hybrid()
402
637
  self._themes["clean"] = Theme.clean()
638
+ self._themes["enterprise_dark"] = Theme.enterprise_dark()
639
+ self._themes["enterprise_light"] = Theme.enterprise_light()
640
+ self._themes["enterprise_hybrid"] = Theme.enterprise_hybrid()
403
641
 
404
642
  def load_themes(self) -> None:
405
643
  """Load all themes from theme directory."""
@@ -0,0 +1,42 @@
1
+ name: enterprise_dark
2
+
3
+ # Classic enterprise dark theme
4
+ # Dark UI chrome with pure black terminal
5
+ # Microsoft-inspired blue accent palette
6
+
7
+ terminal_colors:
8
+ background: "#0c0c0c"
9
+ foreground: "#cccccc"
10
+ cursor: "#ffffff"
11
+ cursorAccent: "#0c0c0c"
12
+ selectionBackground: "#264f78"
13
+ selectionForeground: "#ffffff"
14
+ # Windows Terminal-inspired palette
15
+ black: "#0c0c0c"
16
+ red: "#c50f1f"
17
+ green: "#13a10e"
18
+ yellow: "#c19c00"
19
+ blue: "#0037da"
20
+ magenta: "#881798"
21
+ cyan: "#3a96dd"
22
+ white: "#cccccc"
23
+ brightBlack: "#767676"
24
+ brightRed: "#e74856"
25
+ brightGreen: "#16c60c"
26
+ brightYellow: "#f9f1a5"
27
+ brightBlue: "#3b78ff"
28
+ brightMagenta: "#b4009e"
29
+ brightCyan: "#61d6d6"
30
+ brightWhite: "#f2f2f2"
31
+
32
+ font_family: "Cascadia Code, Consolas, JetBrains Mono, monospace"
33
+ font_size: 14
34
+
35
+ # UI chrome colors
36
+ background_color: "#1e1e1e"
37
+ foreground_color: "#d4d4d4"
38
+ border_color: "#3c3c3c"
39
+ accent_color: "#0078d4"
40
+
41
+ overlay_background: "rgba(30, 30, 30, 0.95)"
42
+ overlay_text_color: "#d4d4d4"
@@ -0,0 +1,44 @@
1
+ name: enterprise_hybrid
2
+
3
+ # Enterprise Hybrid theme
4
+ # Dark UI chrome with white terminal
5
+ # Best of both: comfortable dark UI, high-contrast readable terminal
6
+ # Microsoft-inspired blue accent palette
7
+
8
+ terminal_colors:
9
+ # Light terminal for readability
10
+ background: "#ffffff"
11
+ foreground: "#1e1e1e"
12
+ cursor: "#1e1e1e"
13
+ cursorAccent: "#ffffff"
14
+ selectionBackground: "#add6ff"
15
+ selectionForeground: "#1e1e1e"
16
+ # Light terminal palette
17
+ black: "#1e1e1e"
18
+ red: "#c72e2e"
19
+ green: "#098658"
20
+ yellow: "#795e26"
21
+ blue: "#0451a5"
22
+ magenta: "#a626a4"
23
+ cyan: "#0598bc"
24
+ white: "#d4d4d4"
25
+ brightBlack: "#5a5a5a"
26
+ brightRed: "#e51400"
27
+ brightGreen: "#14a114"
28
+ brightYellow: "#b5a000"
29
+ brightBlue: "#0078d4"
30
+ brightMagenta: "#bc05bc"
31
+ brightCyan: "#00b7c3"
32
+ brightWhite: "#f8f8f8"
33
+
34
+ font_family: "Cascadia Code, Consolas, JetBrains Mono, monospace"
35
+ font_size: 14
36
+
37
+ # Dark UI chrome
38
+ background_color: "#1e1e1e"
39
+ foreground_color: "#d4d4d4"
40
+ border_color: "#3c3c3c"
41
+ accent_color: "#0078d4"
42
+
43
+ overlay_background: "rgba(30, 30, 30, 0.95)"
44
+ overlay_text_color: "#d4d4d4"
@@ -0,0 +1,42 @@
1
+ name: enterprise_light
2
+
3
+ # Classic enterprise light theme
4
+ # Light UI chrome with white terminal
5
+ # Microsoft-inspired blue accent palette
6
+
7
+ terminal_colors:
8
+ background: "#ffffff"
9
+ foreground: "#1e1e1e"
10
+ cursor: "#1e1e1e"
11
+ cursorAccent: "#ffffff"
12
+ selectionBackground: "#add6ff"
13
+ selectionForeground: "#1e1e1e"
14
+ # Light terminal palette (VS Code light-inspired)
15
+ black: "#1e1e1e"
16
+ red: "#c72e2e"
17
+ green: "#098658"
18
+ yellow: "#795e26"
19
+ blue: "#0451a5"
20
+ magenta: "#a626a4"
21
+ cyan: "#0598bc"
22
+ white: "#d4d4d4"
23
+ brightBlack: "#5a5a5a"
24
+ brightRed: "#e51400"
25
+ brightGreen: "#14a114"
26
+ brightYellow: "#b5a000"
27
+ brightBlue: "#0078d4"
28
+ brightMagenta: "#bc05bc"
29
+ brightCyan: "#00b7c3"
30
+ brightWhite: "#f8f8f8"
31
+
32
+ font_family: "Cascadia Code, Consolas, JetBrains Mono, monospace"
33
+ font_size: 14
34
+
35
+ # UI chrome colors - classic light gray
36
+ background_color: "#f3f3f3"
37
+ foreground_color: "#1e1e1e"
38
+ border_color: "#d1d1d1"
39
+ accent_color: "#0078d4"
40
+
41
+ overlay_background: "rgba(243, 243, 243, 0.95)"
42
+ overlay_text_color: "#1e1e1e"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ntermqt
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Modern SSH terminal widget for PyQt6 with credential vault and jump host support
5
5
  Author: Scott Peterman
6
6
  License: GPL-3.0
@@ -42,7 +42,7 @@ PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiK
42
42
 
43
43
  Built for managing hundreds of devices through bastion hosts with hardware security keys.
44
44
 
45
- ![nterm screenshot](https://raw.githubusercontent.com/scottpeterman/nterm/main/screenshots/gruvbox.png)
45
+ ![nterm screenshot](https://raw.githubusercontent.com/scottpeterman/nterm/main/screenshots/slides.gif)
46
46
 
47
47
  ---
48
48
 
@@ -38,6 +38,9 @@ nterm/theme/stylesheet.py
38
38
  nterm/theme/themes/clean.yaml
39
39
  nterm/theme/themes/default.yaml
40
40
  nterm/theme/themes/dracula.yaml
41
+ nterm/theme/themes/enterprise_dark.yaml
42
+ nterm/theme/themes/enterprise_hybrid.yaml
43
+ nterm/theme/themes/enterprise_light.yaml
41
44
  nterm/theme/themes/gruvbox_dark.yaml
42
45
  nterm/theme/themes/gruvbox_hybrid.yaml
43
46
  nterm/theme/themes/gruvbox_light.yaml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ntermqt"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Modern SSH terminal widget for PyQt6 with credential vault and jump host support"
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes