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.
- {sshplex-1.6.1/sshplex.egg-info → sshplex-1.6.3}/PKG-INFO +21 -108
- {sshplex-1.6.1 → sshplex-1.6.3}/README.md +20 -107
- {sshplex-1.6.1 → sshplex-1.6.3}/pyproject.toml +6 -1
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/__init__.py +1 -1
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/cli.py +13 -107
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/config-template.yaml +1 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/cache.py +2 -2
- sshplex-1.6.3/sshplex/lib/commands.py +110 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/config.py +3 -5
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/iterm2_native.py +19 -2
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/tmux.py +16 -34
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/onboarding/wizard.py +1 -1
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/factory.py +132 -131
- sshplex-1.6.3/sshplex/lib/ui/config_editor.py +1814 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/ui/host_selector.py +262 -44
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/ui/session_manager.py +177 -180
- sshplex-1.6.3/sshplex/lib/utils/ssh_config.py +65 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/main.py +15 -113
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/sshplex_connector.py +35 -15
- {sshplex-1.6.1 → sshplex-1.6.3/sshplex.egg-info}/PKG-INFO +21 -108
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/SOURCES.txt +4 -1
- {sshplex-1.6.1 → sshplex-1.6.3}/tests/test_config.py +19 -1
- sshplex-1.6.3/tests/test_main.py +27 -0
- sshplex-1.6.1/sshplex/lib/ui/config_editor.py +0 -973
- {sshplex-1.6.1 → sshplex-1.6.3}/LICENSE +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/MANIFEST.in +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/setup.cfg +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/__init__.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/logger.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/__init__.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/multiplexer/base.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/onboarding/__init__.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/__init__.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/ansible.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/base.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/consul.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/netbox.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/sot/static.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/ui/__init__.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/utils/__init__.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex/lib/utils/iterm2.py +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/dependency_links.txt +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/entry_points.txt +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/requires.txt +0 -0
- {sshplex-1.6.1 → sshplex-1.6.3}/sshplex.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
@@ -5,9 +5,9 @@ import sys
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from . import __version__
|
|
8
|
-
from .lib.
|
|
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
|
|
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
|
|
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())
|
|
@@ -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
|
|
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("
|
|
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
|