sshplex 1.6.0__tar.gz → 1.6.2__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.0/sshplex.egg-info → sshplex-1.6.2}/PKG-INFO +19 -80
- {sshplex-1.6.0 → sshplex-1.6.2}/README.md +18 -79
- {sshplex-1.6.0 → sshplex-1.6.2}/pyproject.toml +1 -1
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/__init__.py +1 -1
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/multiplexer/iterm2_native.py +22 -5
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/multiplexer/tmux.py +25 -3
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/ui/config_editor.py +133 -22
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/ui/host_selector.py +6 -7
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/ui/session_manager.py +211 -67
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/main.py +6 -4
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/sshplex_connector.py +31 -20
- {sshplex-1.6.0 → sshplex-1.6.2/sshplex.egg-info}/PKG-INFO +19 -80
- {sshplex-1.6.0 → sshplex-1.6.2}/LICENSE +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/MANIFEST.in +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/setup.cfg +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/cli.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/config-template.yaml +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/__init__.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/cache.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/config.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/logger.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/multiplexer/__init__.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/multiplexer/base.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/onboarding/__init__.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/onboarding/wizard.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/__init__.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/ansible.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/base.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/consul.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/factory.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/netbox.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/sot/static.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/ui/__init__.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/utils/__init__.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex/lib/utils/iterm2.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex.egg-info/SOURCES.txt +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex.egg-info/dependency_links.txt +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex.egg-info/entry_points.txt +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex.egg-info/requires.txt +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/sshplex.egg-info/top_level.txt +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/tests/test_cache.py +0 -0
- {sshplex-1.6.0 → sshplex-1.6.2}/tests/test_config.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.2
|
|
4
4
|
Summary: Multiplex your SSH connections with style
|
|
5
5
|
Author-email: MJAHED Sabri <contact@sabrimjahed.com>
|
|
6
6
|
License: MIT
|
|
@@ -61,8 +61,8 @@ 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
|
|
64
|
+
- 📦 **3 Mux Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
|
|
65
|
+
- ✏️ **Config Editor** - Built-in YAML editor with validation
|
|
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
|
|
@@ -83,24 +83,9 @@ sshplex
|
|
|
83
83
|
### Prerequisites
|
|
84
84
|
|
|
85
85
|
- Python 3.8+
|
|
86
|
-
- tmux (Linux/macOS) or iTerm2 (macOS)
|
|
86
|
+
- tmux (Linux/macOS) and/or iTerm2 (macOS)
|
|
87
87
|
- SSH key configured for target hosts
|
|
88
88
|
|
|
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
89
|
## Multiplexer Backends
|
|
105
90
|
|
|
106
91
|
| Backend | Platform | Best For |
|
|
@@ -109,68 +94,28 @@ sshplex
|
|
|
109
94
|
| **tmux + iTerm2** | macOS | Native UI + persistence |
|
|
110
95
|
| **iTerm2 native** | macOS | Simple setup, no tmux dependency |
|
|
111
96
|
|
|
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
97
|
|
|
120
|
-
##
|
|
98
|
+
## Local Demo (Consul + Ansible)
|
|
121
99
|
|
|
122
|
-
|
|
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
|
-
```
|
|
100
|
+
This repo includes a small demo setup that uses the same IP (`192.168.31.216`) with different host names.
|
|
131
101
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
import:
|
|
136
|
-
- name: "prod"
|
|
137
|
-
type: netbox
|
|
138
|
-
url: "https://netbox.example.com/"
|
|
139
|
-
token: "your-api-token"
|
|
140
|
-
```
|
|
102
|
+
```bash
|
|
103
|
+
# Start Consul + seed 3 demo nodes
|
|
104
|
+
docker compose -f demo/docker-compose.consul-demo.yml up -d
|
|
141
105
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
sot:
|
|
145
|
-
import:
|
|
146
|
-
- name: "inventory"
|
|
147
|
-
type: ansible
|
|
148
|
-
inventory_paths: ["/path/to/inventory.yml"]
|
|
106
|
+
# Optional: inspect nodes
|
|
107
|
+
curl -s http://localhost:8500/v1/catalog/nodes | jq
|
|
149
108
|
```
|
|
150
109
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
```
|
|
110
|
+
Demo files:
|
|
111
|
+
- `demo/ansible-inventory-demo.yml`
|
|
112
|
+
- `demo/docker-compose.consul-demo.yml`
|
|
113
|
+
- `demo/sshplex.demo.yaml`
|
|
164
114
|
|
|
165
|
-
|
|
115
|
+
Run with the bundled demo config:
|
|
166
116
|
|
|
167
117
|
```bash
|
|
168
|
-
sshplex
|
|
169
|
-
sshplex --onboarding # Interactive setup wizard
|
|
170
|
-
sshplex --debug # Test provider connectivity
|
|
171
|
-
sshplex --show-config # Show config paths
|
|
172
|
-
sshplex --clear-cache # Clear host cache
|
|
173
|
-
sshplex --config /path/to.yml # Use custom config
|
|
118
|
+
sshplex --config demo/sshplex.demo.yaml
|
|
174
119
|
```
|
|
175
120
|
|
|
176
121
|
## Documentation
|
|
@@ -187,14 +132,8 @@ sshplex --config /path/to.yml # Use custom config
|
|
|
187
132
|
# Basic (tmux only)
|
|
188
133
|
pip install sshplex
|
|
189
134
|
|
|
190
|
-
# With Consul support
|
|
191
|
-
pip install "sshplex[consul]"
|
|
192
|
-
|
|
193
|
-
# With iTerm2 native support (macOS)
|
|
194
|
-
pip install "sshplex[iterm2]"
|
|
195
|
-
|
|
196
|
-
# Development
|
|
197
|
-
pip install -e ".[dev]"
|
|
135
|
+
# With Consul,DEV,Iterm2 support
|
|
136
|
+
pip install "sshplex[dev,consul,iterm2]"
|
|
198
137
|
```
|
|
199
138
|
|
|
200
139
|
## Development
|
|
@@ -8,8 +8,8 @@ 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
|
|
11
|
+
- 📦 **3 Mux Backends** - tmux standalone, tmux + iTerm2, or iTerm2 native (macOS)
|
|
12
|
+
- ✏️ **Config Editor** - Built-in YAML editor with validation
|
|
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
|
|
@@ -30,24 +30,9 @@ sshplex
|
|
|
30
30
|
### Prerequisites
|
|
31
31
|
|
|
32
32
|
- Python 3.8+
|
|
33
|
-
- tmux (Linux/macOS) or iTerm2 (macOS)
|
|
33
|
+
- tmux (Linux/macOS) and/or iTerm2 (macOS)
|
|
34
34
|
- SSH key configured for target hosts
|
|
35
35
|
|
|
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
36
|
## Multiplexer Backends
|
|
52
37
|
|
|
53
38
|
| Backend | Platform | Best For |
|
|
@@ -56,68 +41,28 @@ sshplex
|
|
|
56
41
|
| **tmux + iTerm2** | macOS | Native UI + persistence |
|
|
57
42
|
| **iTerm2 native** | macOS | Simple setup, no tmux dependency |
|
|
58
43
|
|
|
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
44
|
|
|
67
|
-
##
|
|
45
|
+
## Local Demo (Consul + Ansible)
|
|
68
46
|
|
|
69
|
-
|
|
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
|
-
```
|
|
47
|
+
This repo includes a small demo setup that uses the same IP (`192.168.31.216`) with different host names.
|
|
78
48
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
import:
|
|
83
|
-
- name: "prod"
|
|
84
|
-
type: netbox
|
|
85
|
-
url: "https://netbox.example.com/"
|
|
86
|
-
token: "your-api-token"
|
|
87
|
-
```
|
|
49
|
+
```bash
|
|
50
|
+
# Start Consul + seed 3 demo nodes
|
|
51
|
+
docker compose -f demo/docker-compose.consul-demo.yml up -d
|
|
88
52
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
sot:
|
|
92
|
-
import:
|
|
93
|
-
- name: "inventory"
|
|
94
|
-
type: ansible
|
|
95
|
-
inventory_paths: ["/path/to/inventory.yml"]
|
|
53
|
+
# Optional: inspect nodes
|
|
54
|
+
curl -s http://localhost:8500/v1/catalog/nodes | jq
|
|
96
55
|
```
|
|
97
56
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
```
|
|
57
|
+
Demo files:
|
|
58
|
+
- `demo/ansible-inventory-demo.yml`
|
|
59
|
+
- `demo/docker-compose.consul-demo.yml`
|
|
60
|
+
- `demo/sshplex.demo.yaml`
|
|
111
61
|
|
|
112
|
-
|
|
62
|
+
Run with the bundled demo config:
|
|
113
63
|
|
|
114
64
|
```bash
|
|
115
|
-
sshplex
|
|
116
|
-
sshplex --onboarding # Interactive setup wizard
|
|
117
|
-
sshplex --debug # Test provider connectivity
|
|
118
|
-
sshplex --show-config # Show config paths
|
|
119
|
-
sshplex --clear-cache # Clear host cache
|
|
120
|
-
sshplex --config /path/to.yml # Use custom config
|
|
65
|
+
sshplex --config demo/sshplex.demo.yaml
|
|
121
66
|
```
|
|
122
67
|
|
|
123
68
|
## Documentation
|
|
@@ -134,14 +79,8 @@ sshplex --config /path/to.yml # Use custom config
|
|
|
134
79
|
# Basic (tmux only)
|
|
135
80
|
pip install sshplex
|
|
136
81
|
|
|
137
|
-
# With Consul support
|
|
138
|
-
pip install "sshplex[consul]"
|
|
139
|
-
|
|
140
|
-
# With iTerm2 native support (macOS)
|
|
141
|
-
pip install "sshplex[iterm2]"
|
|
142
|
-
|
|
143
|
-
# Development
|
|
144
|
-
pip install -e ".[dev]"
|
|
82
|
+
# With Consul,DEV,Iterm2 support
|
|
83
|
+
pip install "sshplex[dev,consul,iterm2]"
|
|
145
84
|
```
|
|
146
85
|
|
|
147
86
|
## 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.2"
|
|
8
8
|
description = "Multiplex your SSH connections with style"
|
|
9
9
|
authors = [{name = "MJAHED Sabri", email = "contact@sabrimjahed.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -7,9 +7,11 @@ Backend options:
|
|
|
7
7
|
- backend: "iterm2-native" - Pure iTerm2 Python API (no tmux dependency)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import contextlib
|
|
11
|
+
import io
|
|
10
12
|
import platform
|
|
11
13
|
import re
|
|
12
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, List, Optional, Tuple
|
|
13
15
|
|
|
14
16
|
from ..logger import get_logger
|
|
15
17
|
from .base import MultiplexerBase
|
|
@@ -66,7 +68,7 @@ class ITerm2NativeManager(MultiplexerBase):
|
|
|
66
68
|
self._check_iterm2_api()
|
|
67
69
|
|
|
68
70
|
# Session tracking: hostname -> SSH command
|
|
69
|
-
self._pending_sessions:
|
|
71
|
+
self._pending_sessions: List[Tuple[str, str]] = []
|
|
70
72
|
|
|
71
73
|
# Broadcast state
|
|
72
74
|
self._broadcast_enabled = False
|
|
@@ -184,7 +186,7 @@ class ITerm2NativeManager(MultiplexerBase):
|
|
|
184
186
|
if command is None:
|
|
185
187
|
command = f"ssh {hostname}"
|
|
186
188
|
|
|
187
|
-
self._pending_sessions
|
|
189
|
+
self._pending_sessions.append((hostname, command))
|
|
188
190
|
self._max_panes_per_tab = max_panes_per_window
|
|
189
191
|
self.logger.info(f"SSHplex: Queued iTerm2 pane for '{hostname}'")
|
|
190
192
|
return True
|
|
@@ -293,7 +295,7 @@ class ITerm2NativeManager(MultiplexerBase):
|
|
|
293
295
|
return
|
|
294
296
|
|
|
295
297
|
# Build sessions list for the async function
|
|
296
|
-
sessions_data = list(self._pending_sessions
|
|
298
|
+
sessions_data = list(self._pending_sessions)
|
|
297
299
|
max_panes = self._max_panes_per_tab
|
|
298
300
|
profile = self._profile
|
|
299
301
|
split_pattern = self._split_pattern
|
|
@@ -544,7 +546,22 @@ class ITerm2NativeManager(MultiplexerBase):
|
|
|
544
546
|
# Run with iTerm2's run_until_complete to maintain connection
|
|
545
547
|
try:
|
|
546
548
|
print(f"\n🚀 Creating iTerm2 session with {len(sessions_data)} SSH connections...")
|
|
547
|
-
|
|
549
|
+
noisy_stderr = io.StringIO()
|
|
550
|
+
with contextlib.redirect_stderr(noisy_stderr):
|
|
551
|
+
iterm2.run_until_complete(_create_sessions)
|
|
552
|
+
|
|
553
|
+
stderr_text = noisy_stderr.getvalue().strip()
|
|
554
|
+
if stderr_text:
|
|
555
|
+
benign_markers = [
|
|
556
|
+
"ConnectionClosedError",
|
|
557
|
+
"CancelledError",
|
|
558
|
+
"sent 1000 (OK)",
|
|
559
|
+
"no close frame received",
|
|
560
|
+
]
|
|
561
|
+
if any(marker in stderr_text for marker in benign_markers):
|
|
562
|
+
self.logger.debug("SSHplex: Suppressed benign iTerm2 websocket shutdown noise")
|
|
563
|
+
else:
|
|
564
|
+
self.logger.warning(f"SSHplex: iTerm2 stderr output: {stderr_text}")
|
|
548
565
|
self.logger.info("SSHplex: iTerm2 session created successfully")
|
|
549
566
|
except Exception as e:
|
|
550
567
|
error_msg = str(e)
|
|
@@ -84,6 +84,28 @@ class TmuxManager(MultiplexerBase):
|
|
|
84
84
|
self.logger.error(f"SSHplex: Failed to initialize tmux server: {e}")
|
|
85
85
|
return False
|
|
86
86
|
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _split_window(window: Any, vertical: bool = True) -> Any:
|
|
89
|
+
"""Split tmux window with libtmux version compatibility."""
|
|
90
|
+
split_window = getattr(window, "split_window", None)
|
|
91
|
+
if callable(split_window):
|
|
92
|
+
try:
|
|
93
|
+
return split_window(vertical=vertical)
|
|
94
|
+
except Exception as exc:
|
|
95
|
+
if "deprecated" not in str(exc).lower() and "removed" not in str(exc).lower():
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
split = getattr(window, "split", None)
|
|
99
|
+
if callable(split):
|
|
100
|
+
try:
|
|
101
|
+
from libtmux.window import PaneDirection
|
|
102
|
+
direction = PaneDirection.Below if vertical else PaneDirection.Right
|
|
103
|
+
return split(direction=direction)
|
|
104
|
+
except Exception:
|
|
105
|
+
return split()
|
|
106
|
+
|
|
107
|
+
raise RuntimeError("No compatible tmux split method found")
|
|
108
|
+
|
|
87
109
|
def create_session(self) -> bool:
|
|
88
110
|
"""Create a new tmux session with SSHplex branding."""
|
|
89
111
|
try:
|
|
@@ -179,20 +201,20 @@ class TmuxManager(MultiplexerBase):
|
|
|
179
201
|
# Additional panes - attempt split with fallback
|
|
180
202
|
vertical_split = (self.current_window_pane_count % 2 == 0)
|
|
181
203
|
try:
|
|
182
|
-
pane = self.current_window
|
|
204
|
+
pane = self._split_window(self.current_window, vertical=vertical_split)
|
|
183
205
|
except Exception as e:
|
|
184
206
|
# Handle "no space" error by resizing or creating a new window
|
|
185
207
|
self.logger.warning(f"Pane split failed ({e}), attempting layout adjustment")
|
|
186
208
|
try:
|
|
187
209
|
# Resize window to fit more panes
|
|
188
210
|
self.current_window.resize(height=80, width=200)
|
|
189
|
-
pane = self.current_window
|
|
211
|
+
pane = self._split_window(self.current_window, vertical=vertical_split)
|
|
190
212
|
except Exception:
|
|
191
213
|
# If still fails, create a new window
|
|
192
214
|
self.logger.info("Creating new window due to insufficient space")
|
|
193
215
|
ensure_window_available()
|
|
194
216
|
vertical_split = True # first split in new window
|
|
195
|
-
pane = self.current_window
|
|
217
|
+
pane = self._split_window(self.current_window, vertical=vertical_split)
|
|
196
218
|
|
|
197
219
|
if pane is None:
|
|
198
220
|
raise RuntimeError(f"Failed to create tmux pane for {hostname}")
|