sshplex 1.6.2__tar.gz → 1.6.3__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 (46) hide show
  1. {sshplex-1.6.2/sshplex.egg-info → sshplex-1.6.3}/PKG-INFO +21 -2
  2. {sshplex-1.6.2 → sshplex-1.6.3}/README.md +20 -1
  3. {sshplex-1.6.2 → sshplex-1.6.3}/pyproject.toml +6 -1
  4. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/__init__.py +1 -1
  5. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/cli.py +13 -107
  6. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/config-template.yaml +1 -0
  7. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/cache.py +2 -2
  8. sshplex-1.6.3/sshplex/lib/commands.py +110 -0
  9. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/config.py +3 -5
  10. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/multiplexer/tmux.py +0 -32
  11. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/onboarding/wizard.py +1 -1
  12. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/factory.py +132 -131
  13. sshplex-1.6.3/sshplex/lib/ui/config_editor.py +1814 -0
  14. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/ui/host_selector.py +257 -38
  15. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/ui/session_manager.py +33 -135
  16. sshplex-1.6.3/sshplex/lib/utils/ssh_config.py +65 -0
  17. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/main.py +12 -110
  18. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/sshplex_connector.py +34 -15
  19. {sshplex-1.6.2 → sshplex-1.6.3/sshplex.egg-info}/PKG-INFO +21 -2
  20. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex.egg-info/SOURCES.txt +4 -1
  21. {sshplex-1.6.2 → sshplex-1.6.3}/tests/test_config.py +19 -1
  22. sshplex-1.6.3/tests/test_main.py +27 -0
  23. sshplex-1.6.2/sshplex/lib/ui/config_editor.py +0 -1049
  24. {sshplex-1.6.2 → sshplex-1.6.3}/LICENSE +0 -0
  25. {sshplex-1.6.2 → sshplex-1.6.3}/MANIFEST.in +0 -0
  26. {sshplex-1.6.2 → sshplex-1.6.3}/setup.cfg +0 -0
  27. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/__init__.py +0 -0
  28. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/logger.py +0 -0
  29. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/multiplexer/__init__.py +0 -0
  30. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/multiplexer/base.py +0 -0
  31. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/multiplexer/iterm2_native.py +0 -0
  32. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/onboarding/__init__.py +0 -0
  33. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/__init__.py +0 -0
  34. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/ansible.py +0 -0
  35. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/base.py +0 -0
  36. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/consul.py +0 -0
  37. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/netbox.py +0 -0
  38. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/sot/static.py +0 -0
  39. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/ui/__init__.py +0 -0
  40. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/utils/__init__.py +0 -0
  41. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex/lib/utils/iterm2.py +0 -0
  42. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex.egg-info/dependency_links.txt +0 -0
  43. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex.egg-info/entry_points.txt +0 -0
  44. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex.egg-info/requires.txt +0 -0
  45. {sshplex-1.6.2 → sshplex-1.6.3}/sshplex.egg-info/top_level.txt +0 -0
  46. {sshplex-1.6.2 → sshplex-1.6.3}/tests/test_cache.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sshplex
3
- Version: 1.6.2
3
+ Version: 1.6.3
4
4
  Summary: Multiplex your SSH connections with style
5
5
  Author-email: MJAHED Sabri <contact@sabrimjahed.com>
6
6
  License: MIT
@@ -62,11 +62,19 @@ SSHplex is a Python-based SSH connection multiplexer with a modern TUI. Connect
62
62
  - 🖥️ **Modern TUI** - Textual-based host selector with search, sort, and multi-select
63
63
  - 🔌 **Multiple Sources** - NetBox, Ansible, Consul, static lists - use them together
64
64
  - 📦 **3 Mux Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
65
- - ✏️ **Config Editor** - Built-in YAML editor with validation
65
+ - ✏️ **Config Editor** - Built-in editor with compact source cards, static host rows, and full YAML pane
66
66
  - 🔄 **Broadcast Input** - Sync commands across multiple SSH sessions
67
67
  - 🔐 **SSH Security** - Configurable host key checking and retry logic
68
68
  - 🚀 **Fast Startup** - Intelligent caching with configurable TTL
69
69
 
70
+ ## What Is New in QoL v2
71
+
72
+ - **Static host manager in UI** - Add and edit static hosts as rows (`name`, `ip`, `alias`, `user`, `port`, `key_path`) instead of raw YAML blobs.
73
+ - **Per-host SSH preview** - Preview effective SSH values from `ssh -G` in settings and from host selector (`o`).
74
+ - **Smarter table columns** - Detect columns from live hosts/cache/imports, including SSH-oriented fields like `alias`, `user`, `port`, and `key_path`.
75
+ - **Better Sources UX** - Provider toggles and collapsible import cards make large source configs easier to navigate.
76
+ - **Rich YAML view** - Side-by-side YAML edit + syntax-highlight preview for full config inspection.
77
+
70
78
  ## Quick Start
71
79
 
72
80
  ```bash
@@ -94,6 +102,17 @@ sshplex
94
102
  | **tmux + iTerm2** | macOS | Native UI + persistence |
95
103
  | **iTerm2 native** | macOS | Simple setup, no tmux dependency |
96
104
 
105
+ ## Sources of Truth
106
+
107
+ | Provider | `type` | Extra Dependency | Best For |
108
+ |----------|--------|------------------|----------|
109
+ | **Static** | `static` | None | Small lists, lab hosts, quick manual entries |
110
+ | **NetBox** | `netbox` | None (included in base install) | Inventory-driven infrastructure with metadata |
111
+ | **Ansible** | `ansible` | None | Reusing existing Ansible inventory files |
112
+ | **Consul** | `consul` | `pip install "sshplex[consul]"` | Service discovery and dynamic node catalogs |
113
+
114
+ Provider activation is controlled by `sot.providers`, and each source is configured as an item in `sot.import`.
115
+
97
116
 
98
117
  ## Local Demo (Consul + Ansible)
99
118
 
@@ -9,11 +9,19 @@ SSHplex is a Python-based SSH connection multiplexer with a modern TUI. Connect
9
9
  - 🖥️ **Modern TUI** - Textual-based host selector with search, sort, and multi-select
10
10
  - 🔌 **Multiple Sources** - NetBox, Ansible, Consul, static lists - use them together
11
11
  - 📦 **3 Mux Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
12
- - ✏️ **Config Editor** - Built-in YAML editor with validation
12
+ - ✏️ **Config Editor** - Built-in editor with compact source cards, static host rows, and full YAML pane
13
13
  - 🔄 **Broadcast Input** - Sync commands across multiple SSH sessions
14
14
  - 🔐 **SSH Security** - Configurable host key checking and retry logic
15
15
  - 🚀 **Fast Startup** - Intelligent caching with configurable TTL
16
16
 
17
+ ## What Is New in QoL v2
18
+
19
+ - **Static host manager in UI** - Add and edit static hosts as rows (`name`, `ip`, `alias`, `user`, `port`, `key_path`) instead of raw YAML blobs.
20
+ - **Per-host SSH preview** - Preview effective SSH values from `ssh -G` in settings and from host selector (`o`).
21
+ - **Smarter table columns** - Detect columns from live hosts/cache/imports, including SSH-oriented fields like `alias`, `user`, `port`, and `key_path`.
22
+ - **Better Sources UX** - Provider toggles and collapsible import cards make large source configs easier to navigate.
23
+ - **Rich YAML view** - Side-by-side YAML edit + syntax-highlight preview for full config inspection.
24
+
17
25
  ## Quick Start
18
26
 
19
27
  ```bash
@@ -41,6 +49,17 @@ sshplex
41
49
  | **tmux + iTerm2** | macOS | Native UI + persistence |
42
50
  | **iTerm2 native** | macOS | Simple setup, no tmux dependency |
43
51
 
52
+ ## Sources of Truth
53
+
54
+ | Provider | `type` | Extra Dependency | Best For |
55
+ |----------|--------|------------------|----------|
56
+ | **Static** | `static` | None | Small lists, lab hosts, quick manual entries |
57
+ | **NetBox** | `netbox` | None (included in base install) | Inventory-driven infrastructure with metadata |
58
+ | **Ansible** | `ansible` | None | Reusing existing Ansible inventory files |
59
+ | **Consul** | `consul` | `pip install "sshplex[consul]"` | Service discovery and dynamic node catalogs |
60
+
61
+ Provider activation is controlled by `sot.providers`, and each source is configured as an item in `sot.import`.
62
+
44
63
 
45
64
  ## Local Demo (Consul + Ansible)
46
65
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sshplex"
7
- version = "1.6.2"
7
+ version = "1.6.3"
8
8
  description = "Multiplex your SSH connections with style"
9
9
  authors = [{name = "MJAHED Sabri", email = "contact@sabrimjahed.com"}]
10
10
  readme = "README.md"
@@ -113,6 +113,11 @@ module = ["consul"]
113
113
  ignore_missing_imports = true
114
114
 
115
115
 
116
+ [[tool.mypy.overrides]]
117
+ module = ["pynetbox", "pynetbox.*", "pyperclip", "pyperclip.*"]
118
+ ignore_missing_imports = true
119
+
120
+
116
121
  [[tool.mypy.overrides]]
117
122
  module = ["sshplex.lib.sot.consul"]
118
123
  ignore_errors = true
@@ -1,4 +1,4 @@
1
1
  """SSHplex - SSH Connection Multiplexer"""
2
- __version__ = "1.6.2"
2
+ __version__ = "1.6.3"
3
3
  __author__ = "MJAHED Sabri"
4
4
  __email__ = "contact@sabrimjahed.com"
@@ -5,9 +5,9 @@ import sys
5
5
  from typing import Any
6
6
 
7
7
  from . import __version__
8
- from .lib.config import get_config_info, load_config
8
+ from .lib.commands import clear_cache, run_debug_mode, show_config_info
9
+ from .lib.config import load_config
9
10
  from .lib.logger import get_logger, setup_logging
10
- from .lib.sot.factory import SoTFactory
11
11
 
12
12
 
13
13
  def main() -> int:
@@ -62,11 +62,15 @@ Examples:
62
62
  logger.info("SSHplex CLI started")
63
63
 
64
64
  if args.clear_cache:
65
- return clear_cache(config, logger)
65
+ return clear_cache(config, logger, no_cache_message="Clearing cache...")
66
66
  elif args.list_providers:
67
67
  return list_providers(config, logger)
68
68
  else:
69
- return debug_mode(config, logger)
69
+ return run_debug_mode(
70
+ config,
71
+ logger,
72
+ footer_note="Note: For the full TUI interface, run the main application",
73
+ )
70
74
  else:
71
75
  # Default to debug mode if no specific action
72
76
  print("SSHplex CLI - Loading configuration...")
@@ -81,7 +85,11 @@ Examples:
81
85
  logger = get_logger()
82
86
  logger.info("SSHplex CLI debug mode started")
83
87
 
84
- return debug_mode(config, logger)
88
+ return run_debug_mode(
89
+ config,
90
+ logger,
91
+ footer_note="Note: For the full TUI interface, run the main application",
92
+ )
85
93
 
86
94
  except FileNotFoundError as e:
87
95
  print(f"Error: {e}")
@@ -99,25 +107,6 @@ Examples:
99
107
  return 1
100
108
 
101
109
 
102
- def show_config_info() -> int:
103
- """Show configuration file paths and status."""
104
- info = get_config_info()
105
-
106
- print("📁 SSHplex Configuration Information")
107
- print("=" * 50)
108
- print(f"Config Directory: {info['default_config_path'].rsplit('/', 1)[0]}")
109
- print(f"Config File: {info['default_config_path']}")
110
- print(f"Config Exists: {'✅ Yes' if info['default_config_exists'] else '❌ No'}")
111
- print(f"Template File: {info['template_path']}")
112
- print(f"Template Exists: {'✅ Yes' if info['template_exists'] else '❌ No'}")
113
- print()
114
-
115
- if not info['default_config_exists']:
116
- print("💡 Run 'sshplex' to create a default configuration file")
117
-
118
- return 0
119
-
120
-
121
110
  def list_providers(config: Any, logger: Any) -> int:
122
111
  """List all configured SoT providers."""
123
112
  logger.info("Listing configured SoT providers")
@@ -156,88 +145,5 @@ def list_providers(config: Any, logger: Any) -> int:
156
145
  return 0
157
146
 
158
147
 
159
- def clear_cache(config: Any, logger: Any) -> int:
160
- """Clear the host cache."""
161
- logger.info("Clearing host cache")
162
-
163
- from .lib.cache import HostCache
164
-
165
- cache = HostCache(
166
- cache_dir=config.cache.cache_dir,
167
- cache_ttl_hours=config.cache.ttl_hours
168
- )
169
-
170
- cache_info = cache.get_cache_info()
171
- if cache_info:
172
- print(f"🗑️ Clearing cache ({cache_info.get('host_count', 0)} hosts, age: {cache_info.get('age_hours', 0):.1f}h)")
173
- else:
174
- print("🗑️ Clearing cache...")
175
-
176
- if cache.clear_cache():
177
- print("✅ Cache cleared successfully")
178
- return 0
179
- else:
180
- print("❌ Failed to clear cache")
181
- return 1
182
-
183
-
184
- def debug_mode(config: Any, logger: Any) -> int:
185
- """Run debug mode - SoT provider connection and host listing test."""
186
- logger.info("Running CLI debug mode - SoT provider connectivity test")
187
-
188
- # Initialize SoT factory
189
- logger.info("Initializing SoT factory")
190
- sot_factory = SoTFactory(config)
191
-
192
- # Check cache status
193
- cache_info = sot_factory.get_cache_info()
194
- if cache_info:
195
- print(f"📦 Cache: {cache_info.get('host_count', 0)} hosts cached ({cache_info.get('age_hours', 0):.1f}h old)")
196
-
197
- # Initialize all providers
198
- if not sot_factory.initialize_providers():
199
- logger.error("Failed to initialize any SoT providers")
200
- print("❌ Failed to initialize any SoT providers")
201
- print("Check your configuration and network connectivity")
202
- return 1
203
-
204
- print(f"✅ Successfully initialized {sot_factory.get_provider_count()} SoT provider(s): {', '.join(sot_factory.get_provider_names())}")
205
-
206
- # Test all connections
207
- logger.info("Testing SoT provider connections...")
208
- connection_results = sot_factory.test_all_connections()
209
-
210
- for provider_name, status in connection_results.items():
211
- if status:
212
- print(f"✅ {provider_name}: Connection successful")
213
- else:
214
- print(f"❌ {provider_name}: Connection failed")
215
-
216
- # Retrieve hosts from all providers
217
- logger.info("Retrieving hosts from all SoT providers...")
218
- hosts = sot_factory.get_all_hosts()
219
-
220
- # Display results
221
- if hosts:
222
- logger.info(f"Successfully retrieved {len(hosts)} hosts")
223
- print(f"\n📋 Found {len(hosts)} hosts from all providers:")
224
- print("-" * 80)
225
- for i, host in enumerate(hosts, 1):
226
- status = getattr(host, 'status', host.metadata.get('status', 'unknown'))
227
- sources = host.metadata.get('sources', ['unknown'])
228
- source_str = ', '.join(sources) if isinstance(sources, list) else str(sources)
229
- print(f"{i:3d}. {host.name:<25} {host.ip:<15} [{status:<8}] ({source_str})")
230
- print("-" * 80)
231
- else:
232
- logger.warning("No hosts found matching the filters")
233
- print("⚠️ No hosts found matching the configured filters")
234
- print("Check your SoT provider filters in the configuration")
235
-
236
- logger.info("SSHplex CLI debug mode completed successfully")
237
- print("\n✅ CLI debug mode completed successfully")
238
- print("Note: For the full TUI interface, run the main application")
239
- return 0
240
-
241
-
242
148
  if __name__ == "__main__":
243
149
  sys.exit(main())
@@ -109,6 +109,7 @@ tmux:
109
109
  iterm2_profile: "Default" # iTerm2 profile name to use
110
110
 
111
111
  ui:
112
+ theme: "textual-dark"
112
113
  show_log_panel: false
113
114
  log_panel_height: 20
114
115
  table_columns: ["name", "ip", "cluster", "tags", "description", "provider"]
@@ -22,13 +22,13 @@ class HostCache:
22
22
  """Initialize the cache manager.
23
23
 
24
24
  Args:
25
- cache_dir: Directory to store cache files (defaults to ~/cache/sshplex)
25
+ cache_dir: Directory to store cache files (defaults to ~/.cache/sshplex)
26
26
  cache_ttl_hours: Cache time-to-live in hours (default 24 hours)
27
27
  """
28
28
  self.logger = get_logger()
29
29
 
30
30
  if cache_dir is None:
31
- cache_dir = os.path.expanduser("~/cache/sshplex")
31
+ cache_dir = os.path.expanduser("~/.cache/sshplex")
32
32
 
33
33
  self.cache_dir = Path(cache_dir)
34
34
  self.cache_ttl = timedelta(hours=cache_ttl_hours)
@@ -0,0 +1,110 @@
1
+ """Shared command helpers for main and CLI entry points."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .cache import HostCache
8
+ from .config import get_config_info
9
+ from .sot.factory import SoTFactory
10
+
11
+
12
+ def show_config_info() -> int:
13
+ """Show configuration file paths and status."""
14
+ info = get_config_info()
15
+
16
+ print("📁 SSHplex Configuration Information")
17
+ print("=" * 50)
18
+ print(f"Config Directory: {info['default_config_path'].rsplit('/', 1)[0]}")
19
+ print(f"Config File: {info['default_config_path']}")
20
+ print(f"Config Exists: {'✅ Yes' if info['default_config_exists'] else '❌ No'}")
21
+ print(f"Template File: {info['template_path']}")
22
+ print(f"Template Exists: {'✅ Yes' if info['template_exists'] else '❌ No'}")
23
+ print()
24
+
25
+ if not info['default_config_exists']:
26
+ print("💡 Run 'sshplex' to create a default configuration file")
27
+
28
+ return 0
29
+
30
+
31
+ def clear_cache(config: Any, logger: Any, no_cache_message: str = "No cache to clear") -> int:
32
+ """Clear the host cache."""
33
+ logger.info("Clearing host cache")
34
+
35
+ cache = HostCache(
36
+ cache_dir=config.cache.cache_dir,
37
+ cache_ttl_hours=config.cache.ttl_hours,
38
+ )
39
+
40
+ cache_info = cache.get_cache_info()
41
+ if cache_info:
42
+ print(
43
+ "🗑️ Clearing cache "
44
+ f"({cache_info.get('host_count', 0)} hosts, age: {cache_info.get('age_hours', 0):.1f}h)"
45
+ )
46
+ else:
47
+ print(f"🗑️ {no_cache_message}")
48
+ return 0
49
+
50
+ if cache.clear_cache():
51
+ print("✅ Cache cleared successfully")
52
+ return 0
53
+
54
+ print("❌ Failed to clear cache")
55
+ return 1
56
+
57
+
58
+ def run_debug_mode(config: Any, logger: Any, footer_note: str = "") -> int:
59
+ """Run provider connectivity + host retrieval debug flow."""
60
+ logger.info("Running debug mode - SoT provider connectivity test")
61
+
62
+ sot_factory = SoTFactory(config)
63
+
64
+ cache_info = sot_factory.get_cache_info()
65
+ if cache_info:
66
+ print(f"📦 Cache: {cache_info.get('host_count', 0)} hosts cached ({cache_info.get('age_hours', 0):.1f}h old)")
67
+
68
+ if not sot_factory.initialize_providers():
69
+ logger.error("Failed to initialize any SoT providers")
70
+ print("❌ Failed to initialize any SoT providers")
71
+ print("Check your configuration and network connectivity")
72
+ return 1
73
+
74
+ print(
75
+ "✅ Successfully initialized "
76
+ f"{sot_factory.get_provider_count()} SoT provider(s): {', '.join(sot_factory.get_provider_names())}"
77
+ )
78
+
79
+ logger.info("Testing SoT provider connections...")
80
+ connection_results = sot_factory.test_all_connections()
81
+
82
+ for provider_name, status in connection_results.items():
83
+ if status:
84
+ print(f"✅ {provider_name}: Connection successful")
85
+ else:
86
+ print(f"❌ {provider_name}: Connection failed")
87
+
88
+ logger.info("Retrieving hosts from all SoT providers...")
89
+ hosts = sot_factory.get_all_hosts()
90
+
91
+ if hosts:
92
+ logger.info(f"Successfully retrieved {len(hosts)} hosts")
93
+ print(f"\n📋 Found {len(hosts)} hosts from all providers:")
94
+ print("-" * 80)
95
+ for i, host in enumerate(hosts, 1):
96
+ status = getattr(host, 'status', host.metadata.get('status', 'unknown'))
97
+ sources = host.metadata.get('sources', ['unknown'])
98
+ source_str = ', '.join(sources) if isinstance(sources, list) else str(sources)
99
+ print(f"{i:3d}. {host.name:<25} {host.ip:<15} [{status:<8}] ({source_str})")
100
+ print("-" * 80)
101
+ else:
102
+ logger.warning("No hosts found matching the filters")
103
+ print("⚠️ No hosts found matching the configured filters")
104
+ print("Check your SoT provider filters in the configuration")
105
+
106
+ logger.info("SSHplex debug mode completed successfully")
107
+ print("\n✅ Debug mode completed successfully")
108
+ if footer_note:
109
+ print(footer_note)
110
+ return 0
@@ -34,6 +34,7 @@ class LoggingConfig(BaseModel):
34
34
 
35
35
  class UIConfig(BaseModel):
36
36
  """User interface configuration."""
37
+ theme: str = "textual-dark"
37
38
  show_log_panel: bool = True
38
39
  log_panel_height: int = 20 # Percentage of screen height
39
40
  table_columns: list = Field(default_factory=lambda: ["name", "ip", "cluster", "role", "tags"])
@@ -190,7 +191,7 @@ class SoTConfig(BaseModel):
190
191
  class CacheConfig(BaseModel):
191
192
  """Host cache configuration."""
192
193
  enabled: bool = True
193
- cache_dir: str = "~/cache/sshplex"
194
+ cache_dir: str = "~/.cache/sshplex"
194
195
  ttl_hours: int = Field(default=24, description="Cache time-to-live in hours")
195
196
 
196
197
 
@@ -283,10 +284,7 @@ def load_config(config_path: Optional[str] = None) -> Config:
283
284
  print(" - netbox.token: Your NetBox API token")
284
285
  print(" - ssh.username: Your SSH username")
285
286
  print(" - ssh.key_path: Path to your SSH private key")
286
- print("\n🚀 Run 'sshplex' again after configuration is complete!")
287
- # Exit gracefully to let user configure
288
- import sys
289
- sys.exit(0)
287
+ print("\n🚀 Continuing with generated defaults. You can adjust settings later in the Config editor (key: e).")
290
288
  except Exception as e:
291
289
  raise FileNotFoundError(f"SSHplex: Could not initialize default config: {e}") from e
292
290
  else:
@@ -12,11 +12,6 @@ from ..logger import get_logger
12
12
  from .base import MultiplexerBase
13
13
 
14
14
 
15
- class TmuxError(Exception):
16
- """Raised when tmux operations fail."""
17
- pass
18
-
19
-
20
15
  class TmuxManager(MultiplexerBase):
21
16
  """tmux implementation for SSHplex multiplexer."""
22
17
 
@@ -517,30 +512,3 @@ class TmuxManager(MultiplexerBase):
517
512
  except Exception as e:
518
513
  self.logger.error(f"SSHplex: Failed to disable broadcast mode: {e}")
519
514
  return False
520
-
521
- def toggle_broadcast(self) -> bool:
522
- """Toggle broadcast mode for all windows in the session."""
523
- try:
524
- if not self.session:
525
- self.logger.error("SSHplex: No session available for broadcast")
526
- return False
527
-
528
- # Check current broadcast state of first window with multiple panes
529
- current_state = False
530
- for window in self.windows.values():
531
- if window and len(window.panes) > 1:
532
- # Get current synchronize-panes setting
533
- result = window.cmd('show-window-options', '-v', 'synchronize-panes')
534
- if result and hasattr(result, 'stdout') and result.stdout:
535
- current_state = result.stdout[0].strip() == 'on'
536
- break
537
-
538
- # Toggle the state
539
- if current_state:
540
- return self.disable_broadcast()
541
- else:
542
- return self.enable_broadcast()
543
-
544
- except Exception as e:
545
- self.logger.error(f"SSHplex: Failed to toggle broadcast mode: {e}")
546
- return False
@@ -340,7 +340,7 @@ class OnboardingWizard:
340
340
  self.console.print("\n🔄 Testing connection...")
341
341
 
342
342
  try:
343
- import pynetbox
343
+ import pynetbox # type: ignore[import-untyped]
344
344
  nb = pynetbox.api(config['url'], token=config['token'])
345
345
  if config.get('verify_ssl') is False:
346
346
  nb.http_session.verify = False