ssh-to-code 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
config_manager.py ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Configuration Manager
4
+ Handle loading and saving SSH host configurations
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from typing import Dict, List, Optional
10
+
11
+
12
+ class ConfigManager:
13
+ """Manage SSH host configurations"""
14
+
15
+ DEFAULT_CONFIG_PATH = os.path.expanduser('~/.ssh-dir-browser.json')
16
+
17
+ def __init__(self, config_path: Optional[str] = None):
18
+ self.config_path = config_path or self.DEFAULT_CONFIG_PATH
19
+ self.config = self._load_config()
20
+
21
+ def _load_config(self) -> Dict:
22
+ """Load configuration from file"""
23
+ if not os.path.exists(self.config_path):
24
+ return {
25
+ 'version': '1.0',
26
+ 'hosts': [],
27
+ 'preferences': {
28
+ 'default_start_path': '~',
29
+ 'save_last_path': True,
30
+ }
31
+ }
32
+
33
+ try:
34
+ with open(self.config_path, 'r') as f:
35
+ return json.load(f)
36
+ except Exception as e:
37
+ print(f"Warning: Failed to load config: {e}")
38
+ return {'version': '1.0', 'hosts': [], 'preferences': {}}
39
+
40
+ def save_config(self) -> bool:
41
+ """Save configuration to file"""
42
+ try:
43
+ # Create directory if needed
44
+ config_dir = os.path.dirname(self.config_path)
45
+ if config_dir and not os.path.exists(config_dir):
46
+ os.makedirs(config_dir)
47
+
48
+ with open(self.config_path, 'w') as f:
49
+ json.dump(self.config, f, indent=2)
50
+ return True
51
+ except Exception as e:
52
+ print(f"Error saving config: {e}")
53
+ return False
54
+
55
+ def get_hosts(self) -> List[Dict]:
56
+ """Get list of configured hosts"""
57
+ return self.config.get('hosts', [])
58
+
59
+ def add_host(self, name: str, hostname: str, username: str,
60
+ port: int = 22, key_file: Optional[str] = None,
61
+ default_path: Optional[str] = None) -> bool:
62
+ """Add a new host configuration"""
63
+ # Check if host already exists
64
+ for host in self.config['hosts']:
65
+ if host['name'] == name:
66
+ print(f"Host '{name}' already exists")
67
+ return False
68
+
69
+ host_config = {
70
+ 'name': name,
71
+ 'hostname': hostname,
72
+ 'username': username,
73
+ 'port': port,
74
+ }
75
+
76
+ if key_file:
77
+ host_config['key_file'] = key_file
78
+ if default_path:
79
+ host_config['default_path'] = default_path
80
+
81
+ self.config['hosts'].append(host_config)
82
+ return self.save_config()
83
+
84
+ def remove_host(self, name: str) -> bool:
85
+ """Remove a host configuration"""
86
+ original_len = len(self.config['hosts'])
87
+ self.config['hosts'] = [h for h in self.config['hosts'] if h['name'] != name]
88
+
89
+ if len(self.config['hosts']) < original_len:
90
+ return self.save_config()
91
+ return False
92
+
93
+ def get_host(self, name: str) -> Optional[Dict]:
94
+ """Get a specific host configuration"""
95
+ for host in self.config['hosts']:
96
+ if host['name'] == name:
97
+ return host
98
+ return None
99
+
100
+ def update_host_last_path(self, name: str, path: str) -> bool:
101
+ """Update the last visited path for a host"""
102
+ for host in self.config['hosts']:
103
+ if host['name'] == name:
104
+ host['last_path'] = path
105
+ return self.save_config()
106
+ return False
107
+
108
+ def get_preference(self, key: str, default=None):
109
+ """Get a preference value"""
110
+ return self.config.get('preferences', {}).get(key, default)
111
+
112
+ def set_preference(self, key: str, value) -> bool:
113
+ """Set a preference value"""
114
+ if 'preferences' not in self.config:
115
+ self.config['preferences'] = {}
116
+ self.config['preferences'][key] = value
117
+ return self.save_config()
118
+
119
+
120
+ def main():
121
+ """Test configuration manager"""
122
+ config = ConfigManager()
123
+
124
+ print("SSH Directory Browser - Configuration Manager")
125
+ print("=" * 50)
126
+
127
+ # List hosts
128
+ hosts = config.get_hosts()
129
+ print(f"\nConfigured hosts: {len(hosts)}")
130
+ for host in hosts:
131
+ print(f" - {host['name']}: {host['username']}@{host['hostname']}:{host.get('port', 22)}")
132
+
133
+ # Show preferences
134
+ print(f"\nPreferences:")
135
+ prefs = config.config.get('preferences', {})
136
+ for key, value in prefs.items():
137
+ print(f" {key}: {value}")
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
host_selector.py ADDED
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SSH Host Selector
4
+ Select from saved SSH hosts and launch the directory browser
5
+ """
6
+
7
+ import sys
8
+ import curses
9
+ from typing import List, Dict, Optional
10
+ from config_manager import ConfigManager
11
+ from ssh_handler import SSHHandler
12
+ from ssh_dir_browser import DirectoryBrowser
13
+
14
+
15
+ class HostSelector:
16
+ """Interactive host selection UI"""
17
+
18
+ def __init__(self, config_manager: ConfigManager):
19
+ self.config_manager = config_manager
20
+ self.hosts = config_manager.get_hosts()
21
+ self.selected_index = 0
22
+ self.scroll_offset = 0
23
+
24
+ def draw_ui(self, stdscr):
25
+ """Draw the host selection UI"""
26
+ stdscr.clear()
27
+ height, width = stdscr.getmaxyx()
28
+
29
+ # Header
30
+ header = "SSH Directory Browser - Select Host"
31
+ stdscr.addstr(0, 0, header[:width-1], curses.A_REVERSE | curses.A_BOLD)
32
+
33
+ # Info line
34
+ if not self.hosts:
35
+ info = "No saved hosts. Press 'n' to add a new host or 'q' to quit."
36
+ stdscr.addstr(2, 2, info[:width-3])
37
+ else:
38
+ info = f"Select a host to connect ({len(self.hosts)} available)"
39
+ stdscr.addstr(2, 2, info[:width-3], curses.A_DIM)
40
+
41
+ # Help line
42
+ help_text = "↑/↓: Navigate | Enter: Connect | 'n': New host | 'd': Delete | 'q': Quit"
43
+ stdscr.addstr(3, 0, help_text[:width-1], curses.A_DIM)
44
+
45
+ # Separator
46
+ stdscr.addstr(4, 0, "─" * (width-1))
47
+
48
+ # Host list
49
+ if self.hosts:
50
+ display_height = height - 7
51
+
52
+ # Calculate visible range
53
+ if self.selected_index < self.scroll_offset:
54
+ self.scroll_offset = self.selected_index
55
+ elif self.selected_index >= self.scroll_offset + display_height:
56
+ self.scroll_offset = self.selected_index - display_height + 1
57
+
58
+ # Display hosts
59
+ for i in range(self.scroll_offset, min(len(self.hosts), self.scroll_offset + display_height)):
60
+ y = 5 + i - self.scroll_offset
61
+ host = self.hosts[i]
62
+
63
+ # Format display
64
+ name = host['name']
65
+ connection = f"{host['username']}@{host['hostname']}:{host.get('port', 22)}"
66
+ display = f" {name:20} → {connection}"
67
+
68
+ # Add default path if exists
69
+ if 'default_path' in host:
70
+ display += f" ({host['default_path']})"
71
+
72
+ # Highlight selected
73
+ attr = curses.A_REVERSE if i == self.selected_index else curses.A_NORMAL
74
+
75
+ try:
76
+ stdscr.addstr(y, 0, display[:width-1], attr)
77
+ except curses.error:
78
+ pass
79
+
80
+ # Status bar
81
+ status_y = height - 2
82
+ stdscr.addstr(status_y, 0, "─" * (width-1))
83
+ status = f"Hosts: {len(self.hosts)} | Selected: {self.selected_index + 1}/{len(self.hosts)}" if self.hosts else "No hosts configured"
84
+ stdscr.addstr(status_y + 1, 0, status[:width-1])
85
+
86
+ stdscr.refresh()
87
+
88
+ def run(self, stdscr):
89
+ """Main event loop"""
90
+ curses.curs_set(0)
91
+ stdscr.keypad(True)
92
+
93
+ while True:
94
+ self.draw_ui(stdscr)
95
+ key = stdscr.getch()
96
+
97
+ if key == ord('q') or key == ord('Q'):
98
+ return None
99
+ elif key == curses.KEY_UP:
100
+ if self.selected_index > 0:
101
+ self.selected_index -= 1
102
+ elif key == curses.KEY_DOWN:
103
+ if self.selected_index < len(self.hosts) - 1:
104
+ self.selected_index += 1
105
+ elif key == ord('\n') or key == curses.KEY_ENTER:
106
+ if self.hosts and self.selected_index < len(self.hosts):
107
+ return self.hosts[self.selected_index]
108
+ elif key == ord('n') or key == ord('N'):
109
+ # Add new host (exit to command line for this)
110
+ return 'NEW_HOST'
111
+ elif key == ord('d') or key == ord('D'):
112
+ # Delete selected host
113
+ if self.hosts and self.selected_index < len(self.hosts):
114
+ host = self.hosts[self.selected_index]
115
+ self.config_manager.remove_host(host['name'])
116
+ self.hosts = self.config_manager.get_hosts()
117
+ if self.selected_index >= len(self.hosts) and self.selected_index > 0:
118
+ self.selected_index -= 1
119
+
120
+
121
+ def connect_to_host(host_config: Dict) -> bool:
122
+ """Connect to a host and launch directory browser"""
123
+ print(f"\nConnecting to {host_config['username']}@{host_config['hostname']}:{host_config.get('port', 22)}...")
124
+
125
+ try:
126
+ ssh = SSHHandler(
127
+ hostname=host_config['hostname'],
128
+ username=host_config['username'],
129
+ port=host_config.get('port', 22),
130
+ key_filename=host_config.get('key_file'),
131
+ use_password=False
132
+ )
133
+
134
+ if not ssh.connect():
135
+ print("Failed to connect to SSH server")
136
+ return False
137
+
138
+ print("Connected successfully!")
139
+
140
+ # Determine start path
141
+ start_path = host_config.get('default_path') or host_config.get('last_path') or '~'
142
+ if start_path == '~':
143
+ home_cmd = "echo $HOME"
144
+ start_path = ssh.execute_command(home_cmd).strip() or "/"
145
+
146
+ # Launch browser
147
+ browser = DirectoryBrowser(ssh, start_path)
148
+ curses.wrapper(browser.run)
149
+
150
+ # Save last path
151
+ config = ConfigManager()
152
+ config.update_host_last_path(host_config['name'], browser.current_path)
153
+
154
+ # Cleanup
155
+ ssh.disconnect()
156
+ print("\nDisconnected. Goodbye!")
157
+ return True
158
+
159
+ except KeyboardInterrupt:
160
+ print("\n\nInterrupted by user")
161
+ return False
162
+ except Exception as e:
163
+ print(f"\nError: {str(e)}")
164
+ return False
165
+
166
+
167
+ def main():
168
+ """Main entry point for host selector"""
169
+ import argparse
170
+
171
+ parser = argparse.ArgumentParser(description="SSH Host Selector")
172
+ parser.add_argument('host_name', nargs='?', help='Saved host name to connect to directly')
173
+ args, remaining = parser.parse_known_args()
174
+
175
+ config = ConfigManager()
176
+
177
+ # If host name provided, connect directly
178
+ if args.host_name:
179
+ host_config = config.get_host(args.host_name)
180
+ if host_config:
181
+ print(f"Connecting to saved host: {args.host_name}")
182
+ connect_to_host(host_config)
183
+ return
184
+ else:
185
+ print(f"❌ Host '{args.host_name}' not found in saved configurations")
186
+ print("\nAvailable hosts:")
187
+ hosts = config.get_hosts()
188
+ if hosts:
189
+ for host in hosts:
190
+ print(f" - {host['name']}")
191
+ else:
192
+ print(" (none)")
193
+ print("\nAdd one with: python save_config.py add")
194
+ sys.exit(1)
195
+
196
+ # If no hosts configured, show help
197
+ hosts = config.get_hosts()
198
+ if not hosts:
199
+ print("SSH Directory Browser - Host Selector")
200
+ print("=" * 50)
201
+ print("\nNo saved hosts found.")
202
+ print("\nYou can:")
203
+ print(" 1. Use the direct connection mode:")
204
+ print(" ./ssh-browse user@hostname")
205
+ print("\n 2. Add a host to configuration:")
206
+ print(" python save_config.py add")
207
+ print("\n 3. Or use the quick add:")
208
+ print(" python save_config.py quick myhost example.com user")
209
+ print("\n 4. Edit the config file manually:")
210
+ print(f" {config.config_path}")
211
+ sys.exit(0)
212
+
213
+ # Show host selector
214
+ try:
215
+ selector = HostSelector(config)
216
+ selected = curses.wrapper(selector.run)
217
+
218
+ if selected is None:
219
+ print("Cancelled.")
220
+ sys.exit(0)
221
+ elif selected == 'NEW_HOST':
222
+ print("\nTo add a new host, use:")
223
+ print(" python save_config.py add")
224
+ print("Or edit the config file:")
225
+ print(f" {config.config_path}")
226
+ sys.exit(0)
227
+ else:
228
+ # Connect to selected host
229
+ connect_to_host(selected)
230
+
231
+ except KeyboardInterrupt:
232
+ print("\n\nInterrupted by user")
233
+ sys.exit(0)
234
+
235
+
236
+ if __name__ == "__main__":
237
+ main()