ntermqt 0.1.0__py3-none-any.whl

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 (52) hide show
  1. nterm/__init__.py +54 -0
  2. nterm/__main__.py +619 -0
  3. nterm/askpass/__init__.py +22 -0
  4. nterm/askpass/server.py +393 -0
  5. nterm/config.py +158 -0
  6. nterm/connection/__init__.py +17 -0
  7. nterm/connection/profile.py +296 -0
  8. nterm/manager/__init__.py +29 -0
  9. nterm/manager/connect_dialog.py +322 -0
  10. nterm/manager/editor.py +262 -0
  11. nterm/manager/io.py +678 -0
  12. nterm/manager/models.py +346 -0
  13. nterm/manager/settings.py +264 -0
  14. nterm/manager/tree.py +493 -0
  15. nterm/resources.py +48 -0
  16. nterm/session/__init__.py +60 -0
  17. nterm/session/askpass_ssh.py +399 -0
  18. nterm/session/base.py +110 -0
  19. nterm/session/interactive_ssh.py +522 -0
  20. nterm/session/pty_transport.py +571 -0
  21. nterm/session/ssh.py +610 -0
  22. nterm/terminal/__init__.py +11 -0
  23. nterm/terminal/bridge.py +83 -0
  24. nterm/terminal/resources/terminal.html +253 -0
  25. nterm/terminal/resources/terminal.js +414 -0
  26. nterm/terminal/resources/xterm-addon-fit.min.js +8 -0
  27. nterm/terminal/resources/xterm-addon-unicode11.min.js +8 -0
  28. nterm/terminal/resources/xterm-addon-web-links.min.js +8 -0
  29. nterm/terminal/resources/xterm.css +209 -0
  30. nterm/terminal/resources/xterm.min.js +8 -0
  31. nterm/terminal/widget.py +380 -0
  32. nterm/theme/__init__.py +10 -0
  33. nterm/theme/engine.py +456 -0
  34. nterm/theme/stylesheet.py +377 -0
  35. nterm/theme/themes/clean.yaml +0 -0
  36. nterm/theme/themes/default.yaml +36 -0
  37. nterm/theme/themes/dracula.yaml +36 -0
  38. nterm/theme/themes/gruvbox_dark.yaml +36 -0
  39. nterm/theme/themes/gruvbox_hybrid.yaml +38 -0
  40. nterm/theme/themes/gruvbox_light.yaml +36 -0
  41. nterm/vault/__init__.py +32 -0
  42. nterm/vault/credential_manager.py +163 -0
  43. nterm/vault/keychain.py +135 -0
  44. nterm/vault/manager_ui.py +962 -0
  45. nterm/vault/profile.py +219 -0
  46. nterm/vault/resolver.py +250 -0
  47. nterm/vault/store.py +642 -0
  48. ntermqt-0.1.0.dist-info/METADATA +327 -0
  49. ntermqt-0.1.0.dist-info/RECORD +52 -0
  50. ntermqt-0.1.0.dist-info/WHEEL +5 -0
  51. ntermqt-0.1.0.dist-info/entry_points.txt +5 -0
  52. ntermqt-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example: Credential Manager with Theme Integration
4
+
5
+ Shows how to use the credential manager with nterm's theme system.
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from PyQt6.QtWidgets import (
12
+ QApplication, QMainWindow, QWidget, QVBoxLayout,
13
+ QHBoxLayout, QComboBox, QLabel, QPushButton,
14
+ )
15
+ from PyQt6.QtCore import Qt
16
+
17
+ # Add parent to path for imports
18
+ sys.path.insert(0, str(Path(__file__).parent.parent))
19
+
20
+ from nterm.vault import CredentialManagerWidget, CredentialResolver, ManagerTheme
21
+ from nterm.theme import Theme, ThemeEngine
22
+
23
+
24
+ class CredentialManagerWindow(QMainWindow):
25
+ """Main window with theme-aware credential manager."""
26
+
27
+ def __init__(self):
28
+ super().__init__()
29
+ self.setWindowTitle("nTerm Credential Manager")
30
+ self.setMinimumSize(900, 700)
31
+
32
+ # Initialize theme engine
33
+ self.theme_engine = ThemeEngine()
34
+ self.theme_engine.load_themes()
35
+
36
+ # Initialize credential resolver (includes store)
37
+ self.resolver = CredentialResolver()
38
+
39
+ # Setup UI
40
+ self._setup_ui()
41
+
42
+ # Apply default theme
43
+ self._apply_theme(self.theme_engine.current)
44
+
45
+ # Try auto-unlock from keychain
46
+ self.manager.try_auto_unlock()
47
+
48
+ def _setup_ui(self):
49
+ central = QWidget()
50
+ self.setCentralWidget(central)
51
+
52
+ layout = QVBoxLayout(central)
53
+ layout.setContentsMargins(0, 0, 0, 0)
54
+ layout.setSpacing(0)
55
+
56
+ # Theme selector bar
57
+ theme_bar = QWidget()
58
+ theme_bar.setObjectName("themeBar")
59
+ theme_layout = QHBoxLayout(theme_bar)
60
+ theme_layout.setContentsMargins(16, 8, 16, 8)
61
+
62
+ theme_layout.addWidget(QLabel("Theme:"))
63
+
64
+ self.theme_combo = QComboBox()
65
+ self.theme_combo.addItems(self.theme_engine.list_themes())
66
+ self.theme_combo.setCurrentText("default")
67
+ self.theme_combo.currentTextChanged.connect(self._on_theme_changed)
68
+ theme_layout.addWidget(self.theme_combo)
69
+
70
+ theme_layout.addStretch()
71
+
72
+ # Keychain management
73
+ from nterm.vault import KeychainIntegration
74
+ if KeychainIntegration.is_available():
75
+ clear_keychain_btn = QPushButton("Clear Keychain Password")
76
+ clear_keychain_btn.clicked.connect(self._clear_keychain)
77
+ theme_layout.addWidget(clear_keychain_btn)
78
+
79
+ layout.addWidget(theme_bar)
80
+
81
+ # Credential manager
82
+ self.manager = CredentialManagerWidget(store=self.resolver.store)
83
+ self.manager.credential_selected.connect(self._on_credential_selected)
84
+ self.manager.vault_unlocked.connect(self._on_vault_unlocked)
85
+ self.manager.vault_locked.connect(self._on_vault_locked)
86
+ layout.addWidget(self.manager)
87
+
88
+ def _apply_theme(self, theme: Theme):
89
+ """Apply theme to entire window."""
90
+ self.theme_engine.current = theme
91
+
92
+ # Apply to credential manager
93
+ self.manager.set_theme(theme)
94
+
95
+ # Style the theme bar to match
96
+ self.setStyleSheet(f"""
97
+ QMainWindow {{
98
+ background-color: {theme.background_color};
99
+ }}
100
+ #themeBar {{
101
+ background-color: {theme.terminal_colors.get('black', '#313244')};
102
+ border-bottom: 1px solid {theme.border_color};
103
+ }}
104
+ #themeBar QLabel {{
105
+ color: {theme.foreground_color};
106
+ font-family: {theme.font_family};
107
+ }}
108
+ #themeBar QComboBox {{
109
+ background-color: {theme.background_color};
110
+ color: {theme.foreground_color};
111
+ border: 1px solid {theme.border_color};
112
+ border-radius: 4px;
113
+ padding: 4px 8px;
114
+ min-width: 120px;
115
+ }}
116
+ #themeBar QPushButton {{
117
+ background-color: {theme.background_color};
118
+ color: {theme.foreground_color};
119
+ border: 1px solid {theme.border_color};
120
+ border-radius: 4px;
121
+ padding: 4px 12px;
122
+ }}
123
+ #themeBar QPushButton:hover {{
124
+ border-color: {theme.accent_color};
125
+ }}
126
+ """)
127
+
128
+ def _on_theme_changed(self, theme_name: str):
129
+ theme = self.theme_engine.get_theme(theme_name)
130
+ if theme:
131
+ self._apply_theme(theme)
132
+
133
+ def _on_credential_selected(self, name: str):
134
+ print(f"Selected credential: {name}")
135
+
136
+ def _on_vault_unlocked(self):
137
+ print("Vault unlocked")
138
+
139
+ def _on_vault_locked(self):
140
+ print("Vault locked")
141
+
142
+ def _clear_keychain(self):
143
+ from nterm.vault import KeychainIntegration
144
+ if KeychainIntegration.clear_master_password():
145
+ from PyQt6.QtWidgets import QMessageBox
146
+ QMessageBox.information(self, "Success", "Keychain password cleared")
147
+ else:
148
+ from PyQt6.QtWidgets import QMessageBox
149
+ QMessageBox.warning(self, "Error", "Failed to clear keychain")
150
+
151
+
152
+ def main():
153
+ app = QApplication(sys.argv)
154
+ app.setStyle("Fusion")
155
+
156
+ window = CredentialManagerWindow()
157
+ window.show()
158
+
159
+ sys.exit(app.exec())
160
+
161
+
162
+ if __name__ == "__main__":
163
+ main()
@@ -0,0 +1,135 @@
1
+ """
2
+ Cross-platform keychain integration for master password storage.
3
+
4
+ Supports:
5
+ - macOS: Keychain
6
+ - Windows: Credential Locker
7
+ - Linux: Secret Service (GNOME Keyring / KWallet)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import logging
12
+ from typing import Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Try to import keyring - it's optional
17
+ try:
18
+ import keyring
19
+ from keyring.errors import PasswordDeleteError, KeyringError
20
+ KEYRING_AVAILABLE = True
21
+ except ImportError:
22
+ KEYRING_AVAILABLE = False
23
+ logger.debug("keyring not available - install with: pip install keyring")
24
+
25
+
26
+ class KeychainIntegration:
27
+ """
28
+ Optional system keychain integration for master password caching.
29
+
30
+ This doesn't replace the vault's encryption - it just caches the
31
+ master password so users don't have to type it every session.
32
+ """
33
+
34
+ SERVICE_NAME = "nterm-vault"
35
+ ACCOUNT_NAME = "master-password"
36
+
37
+ @classmethod
38
+ def is_available(cls) -> bool:
39
+ """Check if system keychain is available and functional."""
40
+ if not KEYRING_AVAILABLE:
41
+ return False
42
+
43
+ try:
44
+ # Probe the keychain backend
45
+ backend = keyring.get_keyring()
46
+ # Check it's not the fail backend
47
+ backend_name = type(backend).__name__
48
+ if "Fail" in backend_name or "Null" in backend_name:
49
+ logger.debug(f"Keyring backend not usable: {backend_name}")
50
+ return False
51
+ logger.debug(f"Keyring backend: {backend_name}")
52
+ return True
53
+ except Exception as e:
54
+ logger.debug(f"Keyring probe failed: {e}")
55
+ return False
56
+
57
+ @classmethod
58
+ def get_backend_name(cls) -> Optional[str]:
59
+ """Get the name of the active keyring backend."""
60
+ if not KEYRING_AVAILABLE:
61
+ return None
62
+ try:
63
+ return type(keyring.get_keyring()).__name__
64
+ except Exception:
65
+ return None
66
+
67
+ @classmethod
68
+ def store_master_password(cls, password: str) -> bool:
69
+ """
70
+ Store master password in system keychain.
71
+
72
+ Args:
73
+ password: The master vault password
74
+
75
+ Returns:
76
+ True if stored successfully
77
+ """
78
+ if not cls.is_available():
79
+ logger.warning("Keychain not available")
80
+ return False
81
+
82
+ try:
83
+ keyring.set_password(cls.SERVICE_NAME, cls.ACCOUNT_NAME, password)
84
+ logger.info("Master password stored in system keychain")
85
+ return True
86
+ except Exception as e:
87
+ logger.error(f"Failed to store password in keychain: {e}")
88
+ return False
89
+
90
+ @classmethod
91
+ def get_master_password(cls) -> Optional[str]:
92
+ """
93
+ Retrieve master password from system keychain.
94
+
95
+ Returns:
96
+ Password if found, None otherwise
97
+ """
98
+ if not KEYRING_AVAILABLE:
99
+ return None
100
+
101
+ try:
102
+ password = keyring.get_password(cls.SERVICE_NAME, cls.ACCOUNT_NAME)
103
+ if password:
104
+ logger.debug("Retrieved master password from keychain")
105
+ return password
106
+ except Exception as e:
107
+ logger.debug(f"Failed to get password from keychain: {e}")
108
+ return None
109
+
110
+ @classmethod
111
+ def clear_master_password(cls) -> bool:
112
+ """
113
+ Remove master password from system keychain.
114
+
115
+ Returns:
116
+ True if removed (or wasn't present)
117
+ """
118
+ if not KEYRING_AVAILABLE:
119
+ return False
120
+
121
+ try:
122
+ keyring.delete_password(cls.SERVICE_NAME, cls.ACCOUNT_NAME)
123
+ logger.info("Master password removed from system keychain")
124
+ return True
125
+ except PasswordDeleteError:
126
+ # Password wasn't stored - that's fine
127
+ return True
128
+ except Exception as e:
129
+ logger.error(f"Failed to remove password from keychain: {e}")
130
+ return False
131
+
132
+ @classmethod
133
+ def has_stored_password(cls) -> bool:
134
+ """Check if a password is stored without retrieving it."""
135
+ return cls.get_master_password() is not None