sshplex 1.6.1__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.1/sshplex.egg-info → sshplex-1.6.3}/PKG-INFO +21 -108
  2. {sshplex-1.6.1 → sshplex-1.6.3}/README.md +20 -107
  3. {sshplex-1.6.1 → sshplex-1.6.3}/pyproject.toml +6 -1
  4. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/__init__.py +1 -1
  5. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/cli.py +13 -107
  6. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/config-template.yaml +1 -0
  7. {sshplex-1.6.1 → 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.1 → sshplex-1.6.3}/sshplex/lib/config.py +3 -5
  10. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/iterm2_native.py +19 -2
  11. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/tmux.py +16 -34
  12. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/onboarding/wizard.py +1 -1
  13. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/factory.py +132 -131
  14. sshplex-1.6.3/sshplex/lib/ui/config_editor.py +1814 -0
  15. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/ui/host_selector.py +262 -44
  16. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/ui/session_manager.py +177 -180
  17. sshplex-1.6.3/sshplex/lib/utils/ssh_config.py +65 -0
  18. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/main.py +15 -113
  19. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/sshplex_connector.py +35 -15
  20. {sshplex-1.6.1 → sshplex-1.6.3/sshplex.egg-info}/PKG-INFO +21 -108
  21. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/SOURCES.txt +4 -1
  22. {sshplex-1.6.1 → sshplex-1.6.3}/tests/test_config.py +19 -1
  23. sshplex-1.6.3/tests/test_main.py +27 -0
  24. sshplex-1.6.1/sshplex/lib/ui/config_editor.py +0 -973
  25. {sshplex-1.6.1 → sshplex-1.6.3}/LICENSE +0 -0
  26. {sshplex-1.6.1 → sshplex-1.6.3}/MANIFEST.in +0 -0
  27. {sshplex-1.6.1 → sshplex-1.6.3}/setup.cfg +0 -0
  28. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/__init__.py +0 -0
  29. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/logger.py +0 -0
  30. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/__init__.py +0 -0
  31. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/base.py +0 -0
  32. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/onboarding/__init__.py +0 -0
  33. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/__init__.py +0 -0
  34. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/ansible.py +0 -0
  35. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/base.py +0 -0
  36. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/consul.py +0 -0
  37. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/netbox.py +0 -0
  38. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/static.py +0 -0
  39. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/ui/__init__.py +0 -0
  40. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/utils/__init__.py +0 -0
  41. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/utils/iterm2.py +0 -0
  42. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/dependency_links.txt +0 -0
  43. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/entry_points.txt +0 -0
  44. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/requires.txt +0 -0
  45. {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/top_level.txt +0 -0
  46. {sshplex-1.6.1 → 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.1
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
@@ -61,12 +61,20 @@ SSHplex is a Python-based SSH connection multiplexer with a modern TUI. Connect
61
61
 
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
- - 📦 **3 Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
65
- - ✏️ **Config Editor** - Built-in YAML editor with validation (`e` key)
64
+ - 📦 **3 Mux Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
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
@@ -83,24 +91,9 @@ sshplex
83
91
  ### Prerequisites
84
92
 
85
93
  - Python 3.8+
86
- - tmux (Linux/macOS) or iTerm2 (macOS)
94
+ - tmux (Linux/macOS) and/or iTerm2 (macOS)
87
95
  - SSH key configured for target hosts
88
96
 
89
- ## Usage
90
-
91
- | Key | Action |
92
- |-----|--------|
93
- | `Space` | Toggle host selection |
94
- | `a` / `d` | Select / Deselect all |
95
- | `Enter` | Connect to selected hosts |
96
- | `/` | Search/filter hosts |
97
- | `p` | Toggle panes/tabs mode |
98
- | `b` | Toggle broadcast mode |
99
- | `e` | Open config editor |
100
- | `s` | Open session manager |
101
- | `h` | Show keyboard shortcuts |
102
- | `q` | Quit |
103
-
104
97
  ## Multiplexer Backends
105
98
 
106
99
  | Backend | Platform | Best For |
@@ -109,58 +102,17 @@ sshplex
109
102
  | **tmux + iTerm2** | macOS | Native UI + persistence |
110
103
  | **iTerm2 native** | macOS | Simple setup, no tmux dependency |
111
104
 
112
- ```yaml
113
- # ~/.config/sshplex/sshplex.yaml
114
- tmux:
115
- backend: "tmux" # or "iterm2-native" on macOS
116
- layout: "tiled"
117
- max_panes_per_window: 5
118
- ```
119
-
120
105
  ## Sources of Truth
121
106
 
122
- ### Static Hosts
123
- ```yaml
124
- sot:
125
- import:
126
- - name: "my-servers"
127
- type: static
128
- hosts:
129
- - {name: "web-01", ip: "192.168.1.10", tags: ["web"]}
130
- ```
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 |
131
113
 
132
- ### NetBox
133
- ```yaml
134
- sot:
135
- import:
136
- - name: "prod"
137
- type: netbox
138
- url: "https://netbox.example.com/"
139
- token: "your-api-token"
140
- ```
114
+ Provider activation is controlled by `sot.providers`, and each source is configured as an item in `sot.import`.
141
115
 
142
- ### Ansible
143
- ```yaml
144
- sot:
145
- import:
146
- - name: "inventory"
147
- type: ansible
148
- inventory_paths: ["/path/to/inventory.yml"]
149
- ```
150
-
151
- ### Consul
152
- ```bash
153
- pip install "sshplex[consul]"
154
- ```
155
- ```yaml
156
- sot:
157
- import:
158
- - name: "dc1"
159
- type: consul
160
- config:
161
- host: "consul.example.com"
162
- token: "your-token"
163
- ```
164
116
 
165
117
  ## Local Demo (Consul + Ansible)
166
118
 
@@ -179,45 +131,12 @@ Demo files:
179
131
  - `demo/docker-compose.consul-demo.yml`
180
132
  - `demo/sshplex.demo.yaml`
181
133
 
182
- Example config snippet:
183
-
184
- ```yaml
185
- sot:
186
- providers: ["ansible", "consul"]
187
- import:
188
- - name: "demo-ansible"
189
- type: ansible
190
- inventory_paths:
191
- - "demo/ansible-inventory-demo.yml"
192
-
193
- - name: "demo-consul"
194
- type: consul
195
- config:
196
- host: "127.0.0.1"
197
- port: 8500
198
- token: ""
199
- scheme: "http"
200
- verify: false
201
- dc: "dc1"
202
- ```
203
-
204
134
  Run with the bundled demo config:
205
135
 
206
136
  ```bash
207
137
  sshplex --config demo/sshplex.demo.yaml
208
138
  ```
209
139
 
210
- ## CLI Reference
211
-
212
- ```bash
213
- sshplex # Launch TUI
214
- sshplex --onboarding # Interactive setup wizard
215
- sshplex --debug # Test provider connectivity
216
- sshplex --show-config # Show config paths
217
- sshplex --clear-cache # Clear host cache
218
- sshplex --config /path/to.yml # Use custom config
219
- ```
220
-
221
140
  ## Documentation
222
141
 
223
142
  | Guide | Description |
@@ -232,14 +151,8 @@ sshplex --config /path/to.yml # Use custom config
232
151
  # Basic (tmux only)
233
152
  pip install sshplex
234
153
 
235
- # With Consul support
236
- pip install "sshplex[consul]"
237
-
238
- # With iTerm2 native support (macOS)
239
- pip install "sshplex[iterm2]"
240
-
241
- # Development
242
- pip install -e ".[dev]"
154
+ # With Consul,DEV,Iterm2 support
155
+ pip install "sshplex[dev,consul,iterm2]"
243
156
  ```
244
157
 
245
158
  ## Development
@@ -8,12 +8,20 @@ SSHplex is a Python-based SSH connection multiplexer with a modern TUI. Connect
8
8
 
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
- - 📦 **3 Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
12
- - ✏️ **Config Editor** - Built-in YAML editor with validation (`e` key)
11
+ - 📦 **3 Mux Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
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
@@ -30,24 +38,9 @@ sshplex
30
38
  ### Prerequisites
31
39
 
32
40
  - Python 3.8+
33
- - tmux (Linux/macOS) or iTerm2 (macOS)
41
+ - tmux (Linux/macOS) and/or iTerm2 (macOS)
34
42
  - SSH key configured for target hosts
35
43
 
36
- ## Usage
37
-
38
- | Key | Action |
39
- |-----|--------|
40
- | `Space` | Toggle host selection |
41
- | `a` / `d` | Select / Deselect all |
42
- | `Enter` | Connect to selected hosts |
43
- | `/` | Search/filter hosts |
44
- | `p` | Toggle panes/tabs mode |
45
- | `b` | Toggle broadcast mode |
46
- | `e` | Open config editor |
47
- | `s` | Open session manager |
48
- | `h` | Show keyboard shortcuts |
49
- | `q` | Quit |
50
-
51
44
  ## Multiplexer Backends
52
45
 
53
46
  | Backend | Platform | Best For |
@@ -56,58 +49,17 @@ sshplex
56
49
  | **tmux + iTerm2** | macOS | Native UI + persistence |
57
50
  | **iTerm2 native** | macOS | Simple setup, no tmux dependency |
58
51
 
59
- ```yaml
60
- # ~/.config/sshplex/sshplex.yaml
61
- tmux:
62
- backend: "tmux" # or "iterm2-native" on macOS
63
- layout: "tiled"
64
- max_panes_per_window: 5
65
- ```
66
-
67
52
  ## Sources of Truth
68
53
 
69
- ### Static Hosts
70
- ```yaml
71
- sot:
72
- import:
73
- - name: "my-servers"
74
- type: static
75
- hosts:
76
- - {name: "web-01", ip: "192.168.1.10", tags: ["web"]}
77
- ```
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 |
78
60
 
79
- ### NetBox
80
- ```yaml
81
- sot:
82
- import:
83
- - name: "prod"
84
- type: netbox
85
- url: "https://netbox.example.com/"
86
- token: "your-api-token"
87
- ```
61
+ Provider activation is controlled by `sot.providers`, and each source is configured as an item in `sot.import`.
88
62
 
89
- ### Ansible
90
- ```yaml
91
- sot:
92
- import:
93
- - name: "inventory"
94
- type: ansible
95
- inventory_paths: ["/path/to/inventory.yml"]
96
- ```
97
-
98
- ### Consul
99
- ```bash
100
- pip install "sshplex[consul]"
101
- ```
102
- ```yaml
103
- sot:
104
- import:
105
- - name: "dc1"
106
- type: consul
107
- config:
108
- host: "consul.example.com"
109
- token: "your-token"
110
- ```
111
63
 
112
64
  ## Local Demo (Consul + Ansible)
113
65
 
@@ -126,45 +78,12 @@ Demo files:
126
78
  - `demo/docker-compose.consul-demo.yml`
127
79
  - `demo/sshplex.demo.yaml`
128
80
 
129
- Example config snippet:
130
-
131
- ```yaml
132
- sot:
133
- providers: ["ansible", "consul"]
134
- import:
135
- - name: "demo-ansible"
136
- type: ansible
137
- inventory_paths:
138
- - "demo/ansible-inventory-demo.yml"
139
-
140
- - name: "demo-consul"
141
- type: consul
142
- config:
143
- host: "127.0.0.1"
144
- port: 8500
145
- token: ""
146
- scheme: "http"
147
- verify: false
148
- dc: "dc1"
149
- ```
150
-
151
81
  Run with the bundled demo config:
152
82
 
153
83
  ```bash
154
84
  sshplex --config demo/sshplex.demo.yaml
155
85
  ```
156
86
 
157
- ## CLI Reference
158
-
159
- ```bash
160
- sshplex # Launch TUI
161
- sshplex --onboarding # Interactive setup wizard
162
- sshplex --debug # Test provider connectivity
163
- sshplex --show-config # Show config paths
164
- sshplex --clear-cache # Clear host cache
165
- sshplex --config /path/to.yml # Use custom config
166
- ```
167
-
168
87
  ## Documentation
169
88
 
170
89
  | Guide | Description |
@@ -179,14 +98,8 @@ sshplex --config /path/to.yml # Use custom config
179
98
  # Basic (tmux only)
180
99
  pip install sshplex
181
100
 
182
- # With Consul support
183
- pip install "sshplex[consul]"
184
-
185
- # With iTerm2 native support (macOS)
186
- pip install "sshplex[iterm2]"
187
-
188
- # Development
189
- pip install -e ".[dev]"
101
+ # With Consul,DEV,Iterm2 support
102
+ pip install "sshplex[dev,consul,iterm2]"
190
103
  ```
191
104
 
192
105
  ## Development
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sshplex"
7
- version = "1.6.1"
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.1"
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