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.
save_config.py ADDED
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interactive SSH Configuration Manager
4
+ Easily save and manage frequently used SSH connections
5
+ """
6
+
7
+ import sys
8
+ import os
9
+
10
+ # Add current directory to path
11
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
12
+
13
+ from config_manager import ConfigManager
14
+
15
+
16
+ def print_header(text):
17
+ print("\n" + "=" * 60)
18
+ print(f" {text}")
19
+ print("=" * 60 + "\n")
20
+
21
+
22
+ def add_host_interactive():
23
+ """Interactive host addition"""
24
+ print_header("Add New SSH Host Configuration")
25
+
26
+ config = ConfigManager()
27
+
28
+ # Get host details
29
+ print("Enter the SSH connection details:\n")
30
+
31
+ name = input("Host nickname (e.g., 'my-ec2', 'production'): ").strip()
32
+ if not name:
33
+ print("❌ Name is required")
34
+ return False
35
+
36
+ # Check if already exists
37
+ existing = config.get_host(name)
38
+ if existing:
39
+ print(f"\n⚠️ Host '{name}' already exists!")
40
+ print(f" Current config: {existing['username']}@{existing['hostname']}")
41
+ overwrite = input(" Overwrite? (yes/no): ").strip().lower()
42
+ if overwrite != 'yes':
43
+ print("Cancelled.")
44
+ return False
45
+ config.remove_host(name)
46
+
47
+ hostname = input("Hostname or IP (e.g., 'example.com', '1.2.3.4'): ").strip()
48
+ if not hostname:
49
+ print("❌ Hostname is required")
50
+ return False
51
+
52
+ username = input("Username (e.g., 'ubuntu', 'ec2-user', 'root'): ").strip()
53
+ if not username:
54
+ print("❌ Username is required")
55
+ return False
56
+
57
+ port_input = input("Port (default: 22): ").strip()
58
+ port = int(port_input) if port_input else 22
59
+
60
+ key_file = input("Path to SSH key file (optional, press Enter to skip): ").strip()
61
+ if key_file:
62
+ key_file = os.path.expanduser(key_file)
63
+ if not os.path.exists(key_file):
64
+ print(f"⚠️ Warning: Key file not found: {key_file}")
65
+ proceed = input(" Save anyway? (yes/no): ").strip().lower()
66
+ if proceed != 'yes':
67
+ return False
68
+ else:
69
+ key_file = None
70
+
71
+ default_path = input("Default starting directory (optional, press Enter to skip): ").strip()
72
+ if not default_path:
73
+ default_path = None
74
+
75
+ # Confirm
76
+ print("\n" + "-" * 60)
77
+ print("Configuration Summary:")
78
+ print("-" * 60)
79
+ print(f" Name: {name}")
80
+ print(f" Connection: {username}@{hostname}:{port}")
81
+ if key_file:
82
+ print(f" Key file: {key_file}")
83
+ if default_path:
84
+ print(f" Start path: {default_path}")
85
+ print("-" * 60)
86
+
87
+ confirm = input("\nSave this configuration? (yes/no): ").strip().lower()
88
+ if confirm == 'yes':
89
+ success = config.add_host(
90
+ name=name,
91
+ hostname=hostname,
92
+ username=username,
93
+ port=port,
94
+ key_file=key_file,
95
+ default_path=default_path
96
+ )
97
+
98
+ if success:
99
+ print(f"\n✅ Configuration '{name}' saved successfully!")
100
+ print(f"\nYou can now connect with:")
101
+ print(f" ./ssh-browse {name}")
102
+ print(f" # or")
103
+ print(f" python ssh_dir_browser.py {username}@{hostname} -p {port}", end="")
104
+ if key_file:
105
+ print(f" -i {key_file}", end="")
106
+ print()
107
+ return True
108
+ else:
109
+ print("\n❌ Failed to save configuration")
110
+ return False
111
+ else:
112
+ print("Cancelled.")
113
+ return False
114
+
115
+
116
+ def list_hosts():
117
+ """List all saved hosts"""
118
+ print_header("Saved SSH Host Configurations")
119
+
120
+ config = ConfigManager()
121
+ hosts = config.get_hosts()
122
+
123
+ if not hosts:
124
+ print("No saved configurations found.\n")
125
+ print("💡 Add one with: python save_config.py add")
126
+ return
127
+
128
+ print(f"Found {len(hosts)} saved configuration(s):\n")
129
+
130
+ for i, host in enumerate(hosts, 1):
131
+ print(f"{i}. {host['name']}")
132
+ print(f" Connection: {host['username']}@{host['hostname']}:{host.get('port', 22)}")
133
+ if 'key_file' in host:
134
+ print(f" Key file: {host['key_file']}")
135
+ if 'default_path' in host:
136
+ print(f" Start path: {host['default_path']}")
137
+ if 'last_path' in host:
138
+ print(f" Last used: {host['last_path']}")
139
+ print()
140
+
141
+ print("Connect with: ./ssh-browse <name>")
142
+ print(f"Config file: {config.config_path}\n")
143
+
144
+
145
+ def remove_host_interactive():
146
+ """Interactive host removal"""
147
+ print_header("Remove SSH Host Configuration")
148
+
149
+ config = ConfigManager()
150
+ hosts = config.get_hosts()
151
+
152
+ if not hosts:
153
+ print("No saved configurations found.\n")
154
+ return
155
+
156
+ print("Saved configurations:\n")
157
+ for i, host in enumerate(hosts, 1):
158
+ print(f"{i}. {host['name']} ({host['username']}@{host['hostname']})")
159
+
160
+ print()
161
+ choice = input("Enter name or number to remove (or 'cancel'): ").strip()
162
+
163
+ if choice.lower() == 'cancel':
164
+ print("Cancelled.")
165
+ return
166
+
167
+ # Try as number first
168
+ host_to_remove = None
169
+ try:
170
+ idx = int(choice) - 1
171
+ if 0 <= idx < len(hosts):
172
+ host_to_remove = hosts[idx]['name']
173
+ except ValueError:
174
+ host_to_remove = choice
175
+
176
+ if host_to_remove:
177
+ confirm = input(f"Remove '{host_to_remove}'? (yes/no): ").strip().lower()
178
+ if confirm == 'yes':
179
+ if config.remove_host(host_to_remove):
180
+ print(f"✅ Removed '{host_to_remove}'")
181
+ else:
182
+ print(f"❌ Host '{host_to_remove}' not found")
183
+ else:
184
+ print("Cancelled.")
185
+
186
+
187
+ def quick_add_from_command():
188
+ """Quick add from command line arguments"""
189
+ if len(sys.argv) < 4:
190
+ print("Usage: python save_config.py quick <name> <user@host> [options]")
191
+ print("\nOptions:")
192
+ print(" -i <key_file> Path to SSH key")
193
+ print(" -p <port> SSH port (default: 22)")
194
+ print(" --path <dir> Default starting directory")
195
+ print("\nExample:")
196
+ print(" python save_config.py quick myserver ubuntu@example.com -i ~/key.pem --path /var/www")
197
+ return
198
+
199
+ name = sys.argv[2]
200
+ user_host = sys.argv[3]
201
+
202
+ if '@' not in user_host:
203
+ print("❌ Invalid format. Use: user@hostname")
204
+ return
205
+
206
+ username, hostname = user_host.split('@', 1)
207
+
208
+ # Parse options
209
+ port = 22
210
+ key_file = None
211
+ default_path = None
212
+
213
+ i = 4
214
+ while i < len(sys.argv):
215
+ if sys.argv[i] == '-i' and i + 1 < len(sys.argv):
216
+ key_file = os.path.expanduser(sys.argv[i + 1])
217
+ i += 2
218
+ elif sys.argv[i] == '-p' and i + 1 < len(sys.argv):
219
+ port = int(sys.argv[i + 1])
220
+ i += 2
221
+ elif sys.argv[i] == '--path' and i + 1 < len(sys.argv):
222
+ default_path = sys.argv[i + 1]
223
+ i += 2
224
+ else:
225
+ i += 1
226
+
227
+ config = ConfigManager()
228
+
229
+ if config.add_host(name, hostname, username, port, key_file, default_path):
230
+ print(f"✅ Configuration '{name}' saved!")
231
+ print(f"\nConnect with: ./ssh-browse {name}")
232
+ else:
233
+ print(f"❌ Failed to save configuration")
234
+
235
+
236
+ def show_help():
237
+ """Show help message"""
238
+ print("""
239
+ SSH Directory Browser - Configuration Manager
240
+ ==============================================
241
+
242
+ USAGE:
243
+ python save_config.py [command]
244
+
245
+ COMMANDS:
246
+ add, a Add new host configuration (interactive)
247
+ list, ls, l List all saved configurations
248
+ remove, rm, r Remove a host configuration
249
+ quick <name> ... Quick add from command line
250
+ help, h Show this help message
251
+
252
+ EXAMPLES:
253
+
254
+ 1. Interactive mode (recommended for first time):
255
+ python save_config.py add
256
+
257
+ 2. Quick add from command line:
258
+ python save_config.py quick myec2 ubuntu@ec2-1-2-3-4.compute.amazonaws.com -i ~/aws-key.pem
259
+
260
+ 3. List all saved hosts:
261
+ python save_config.py list
262
+
263
+ 4. Remove a host:
264
+ python save_config.py remove
265
+
266
+ 5. After saving, connect with:
267
+ ./ssh-browse myec2
268
+
269
+ CONFIGURATION FILE:
270
+ Location: ~/.ssh-dir-browser.json
271
+ Format: JSON
272
+
273
+ DIRECT EDITING:
274
+ You can also edit the config file directly:
275
+ nano ~/.ssh-dir-browser.json
276
+
277
+ Example format:
278
+ {
279
+ "version": "1.0",
280
+ "hosts": [
281
+ {
282
+ "name": "myserver",
283
+ "hostname": "example.com",
284
+ "username": "user",
285
+ "port": 22,
286
+ "key_file": "~/.ssh/id_rsa",
287
+ "default_path": "/var/www"
288
+ }
289
+ ]
290
+ }
291
+ """)
292
+
293
+
294
+ def main():
295
+ """Main entry point"""
296
+ if len(sys.argv) < 2:
297
+ # Interactive mode by default
298
+ while True:
299
+ print("\n" + "=" * 60)
300
+ print(" SSH Directory Browser - Configuration Manager")
301
+ print("=" * 60)
302
+ print("\n1. Add new host")
303
+ print("2. List saved hosts")
304
+ print("3. Remove host")
305
+ print("4. Exit")
306
+
307
+ choice = input("\nSelect option (1-4): ").strip()
308
+
309
+ if choice == '1':
310
+ add_host_interactive()
311
+ elif choice == '2':
312
+ list_hosts()
313
+ elif choice == '3':
314
+ remove_host_interactive()
315
+ elif choice == '4':
316
+ print("\nGoodbye!")
317
+ break
318
+ else:
319
+ print("Invalid option")
320
+ return
321
+
322
+ command = sys.argv[1].lower()
323
+
324
+ if command in ['add', 'a']:
325
+ add_host_interactive()
326
+ elif command in ['list', 'ls', 'l']:
327
+ list_hosts()
328
+ elif command in ['remove', 'rm', 'r']:
329
+ remove_host_interactive()
330
+ elif command == 'quick':
331
+ quick_add_from_command()
332
+ elif command in ['help', 'h', '--help', '-h']:
333
+ show_help()
334
+ else:
335
+ print(f"Unknown command: {command}")
336
+ print("Run 'python save_config.py help' for usage information")
337
+
338
+
339
+ if __name__ == "__main__":
340
+ main()
ssh_dir_browser.py ADDED
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SSH Directory Browser with VS Code Integration
4
+ Browse remote directories via SSH and open them in VS Code
5
+ """
6
+
7
+ import curses
8
+ import sys
9
+ import subprocess
10
+ import os
11
+ from typing import List, Optional, Tuple
12
+ import argparse
13
+
14
+
15
+ class DirectoryBrowser:
16
+ """Interactive terminal-based directory browser"""
17
+
18
+ def __init__(self, ssh_handler, start_path: str = "/"):
19
+ self.ssh_handler = ssh_handler
20
+ self.current_path = start_path
21
+ self.selected_index = 0
22
+ self.scroll_offset = 0
23
+ self.items: List[Tuple[str, str]] = [] # (name, type)
24
+ self.status_message = ""
25
+
26
+ def get_directory_contents(self) -> List[Tuple[str, str]]:
27
+ """Get directory contents from remote server"""
28
+ try:
29
+ # List directory with details (-F adds indicators for file types)
30
+ cmd = f"ls -1F '{self.current_path}'"
31
+ output = self.ssh_handler.execute_command(cmd)
32
+
33
+ if not output:
34
+ return []
35
+
36
+ items = []
37
+ # Add parent directory option if not at root
38
+ if self.current_path != "/":
39
+ items.append(("..", "dir"))
40
+
41
+ for line in output.strip().split('\n'):
42
+ if not line:
43
+ continue
44
+
45
+ # Check file type indicator
46
+ if line.endswith('/'):
47
+ items.append((line[:-1], "dir"))
48
+ elif line.endswith('*'):
49
+ items.append((line[:-1], "exec"))
50
+ elif line.endswith('@'):
51
+ items.append((line[:-1], "link"))
52
+ else:
53
+ items.append((line, "file"))
54
+
55
+ return items
56
+ except Exception as e:
57
+ self.status_message = f"Error: {str(e)}"
58
+ return []
59
+
60
+ def navigate_to(self, item_name: str):
61
+ """Navigate to a directory"""
62
+ if item_name == "..":
63
+ # Go to parent directory
64
+ self.current_path = os.path.dirname(self.current_path)
65
+ if not self.current_path:
66
+ self.current_path = "/"
67
+ else:
68
+ # Go to subdirectory
69
+ if self.current_path == "/":
70
+ self.current_path = f"/{item_name}"
71
+ else:
72
+ self.current_path = f"{self.current_path}/{item_name}"
73
+
74
+ self.selected_index = 0
75
+ self.scroll_offset = 0
76
+ self.items = self.get_directory_contents()
77
+
78
+ def create_folder(self, stdscr) -> Optional[str]:
79
+ """Prompt user to create a new folder"""
80
+ height, width = stdscr.getmaxyx()
81
+
82
+ # Create input window
83
+ input_win = curses.newwin(5, min(60, width - 4), height // 2 - 2, (width - min(60, width - 4)) // 2)
84
+ input_win.box()
85
+ input_win.addstr(1, 2, "Create New Folder:", curses.A_BOLD)
86
+ input_win.addstr(2, 2, "Name: ")
87
+ input_win.refresh()
88
+
89
+ # Enable cursor for input
90
+ curses.curs_set(1)
91
+ curses.echo()
92
+
93
+ # Get folder name
94
+ folder_name = ""
95
+ try:
96
+ folder_name = input_win.getstr(2, 8, 40).decode('utf-8').strip()
97
+ except:
98
+ pass
99
+ finally:
100
+ curses.noecho()
101
+ curses.curs_set(0)
102
+
103
+ if not folder_name:
104
+ return None
105
+
106
+ # Validate folder name
107
+ if '/' in folder_name or folder_name in ['.', '..']:
108
+ return f"Invalid folder name: {folder_name}"
109
+
110
+ # Create folder on remote server
111
+ try:
112
+ new_path = os.path.join(self.current_path, folder_name)
113
+ cmd = f"mkdir -p '{new_path}'"
114
+ self.ssh_handler.execute_command(cmd)
115
+ return f"Created folder: {folder_name}"
116
+ except Exception as e:
117
+ return f"Error creating folder: {str(e)}"
118
+
119
+ def open_in_vscode(self):
120
+ """Open current directory in VS Code"""
121
+ try:
122
+ host = self.ssh_handler.hostname
123
+ user = self.ssh_handler.username
124
+ port = self.ssh_handler.port
125
+
126
+ # VS Code Remote-SSH URI format
127
+ # vscode://vscode-remote/ssh-remote+user@host:port/path
128
+ remote_uri = f"vscode://vscode-remote/ssh-remote+{user}@{host}"
129
+ if port != 22:
130
+ remote_uri += f":{port}"
131
+ remote_uri += self.current_path
132
+
133
+ # Open VS Code
134
+ subprocess.Popen(['code', '--remote', f'ssh-remote+{user}@{host}', self.current_path],
135
+ stdout=subprocess.DEVNULL,
136
+ stderr=subprocess.DEVNULL)
137
+
138
+ return True, f"Opening {self.current_path} in VS Code..."
139
+ except Exception as e:
140
+ return False, f"Error opening VS Code: {str(e)}"
141
+
142
+ def draw_ui(self, stdscr):
143
+ """Draw the terminal UI"""
144
+ stdscr.clear()
145
+ height, width = stdscr.getmaxyx()
146
+
147
+ # Header
148
+ header = f"SSH Directory Browser - {self.ssh_handler.username}@{self.ssh_handler.hostname}"
149
+ stdscr.addstr(0, 0, header[:width-1], curses.A_REVERSE)
150
+
151
+ # Current path
152
+ path_line = f"Path: {self.current_path}"
153
+ stdscr.addstr(1, 0, path_line[:width-1], curses.A_BOLD)
154
+
155
+ # Help line
156
+ help_text = "↑/↓: Navigate | Enter: Open | 'o': VS Code | 'n': New Folder | 'r': Refresh | 'q': Quit"
157
+ stdscr.addstr(2, 0, help_text[:width-1], curses.A_DIM)
158
+
159
+ # Separator
160
+ stdscr.addstr(3, 0, "─" * (width-1))
161
+
162
+ # Directory contents
163
+ display_height = height - 6 # Reserve space for header, footer, and status
164
+
165
+ # Calculate visible range
166
+ if self.selected_index < self.scroll_offset:
167
+ self.scroll_offset = self.selected_index
168
+ elif self.selected_index >= self.scroll_offset + display_height:
169
+ self.scroll_offset = self.selected_index - display_height + 1
170
+
171
+ # Display items
172
+ for i in range(self.scroll_offset, min(len(self.items), self.scroll_offset + display_height)):
173
+ y = 4 + i - self.scroll_offset
174
+ name, item_type = self.items[i]
175
+
176
+ # Format display
177
+ if item_type == "dir":
178
+ display = f"📁 {name}/"
179
+ attr = curses.A_BOLD
180
+ elif item_type == "exec":
181
+ display = f"⚙️ {name}*"
182
+ attr = curses.A_NORMAL
183
+ elif item_type == "link":
184
+ display = f"🔗 {name}@"
185
+ attr = curses.A_DIM
186
+ else:
187
+ display = f"📄 {name}"
188
+ attr = curses.A_NORMAL
189
+
190
+ # Highlight selected item
191
+ if i == self.selected_index:
192
+ attr |= curses.A_REVERSE
193
+
194
+ try:
195
+ stdscr.addstr(y, 2, display[:width-3], attr)
196
+ except curses.error:
197
+ pass # Ignore errors from writing to last line
198
+
199
+ # Status bar
200
+ status_y = height - 2
201
+ stdscr.addstr(status_y, 0, "─" * (width-1))
202
+
203
+ if self.status_message:
204
+ status = self.status_message[:width-1]
205
+ stdscr.addstr(status_y + 1, 0, status, curses.A_BOLD)
206
+ else:
207
+ info = f"Items: {len(self.items)} | Selected: {self.selected_index + 1}/{len(self.items)}"
208
+ stdscr.addstr(status_y + 1, 0, info[:width-1])
209
+
210
+ stdscr.refresh()
211
+
212
+ def run(self, stdscr):
213
+ """Main event loop"""
214
+ curses.curs_set(0) # Hide cursor
215
+ stdscr.keypad(True)
216
+
217
+ # Initialize colors if available
218
+ if curses.has_colors():
219
+ curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
220
+ curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
221
+
222
+ # Load initial directory
223
+ self.items = self.get_directory_contents()
224
+
225
+ while True:
226
+ self.draw_ui(stdscr)
227
+
228
+ # Get user input
229
+ key = stdscr.getch()
230
+
231
+ if key == ord('q') or key == ord('Q'):
232
+ break
233
+ elif key == curses.KEY_UP:
234
+ if self.selected_index > 0:
235
+ self.selected_index -= 1
236
+ self.status_message = ""
237
+ elif key == curses.KEY_DOWN:
238
+ if self.selected_index < len(self.items) - 1:
239
+ self.selected_index += 1
240
+ self.status_message = ""
241
+ elif key == ord('\n') or key == curses.KEY_ENTER:
242
+ # Enter selected item
243
+ if self.items and self.selected_index < len(self.items):
244
+ name, item_type = self.items[self.selected_index]
245
+ if item_type == "dir" or name == "..":
246
+ self.navigate_to(name)
247
+ self.status_message = ""
248
+ else:
249
+ self.status_message = f"'{name}' is not a directory"
250
+ elif key == ord('o') or key == ord('O'):
251
+ # Open in VS Code
252
+ success, message = self.open_in_vscode()
253
+ self.status_message = message
254
+ if success:
255
+ # Wait a moment to show the message, then exit
256
+ stdscr.timeout(2000)
257
+ stdscr.getch()
258
+ break
259
+ elif key == ord('h') or key == ord('H'):
260
+ # Go to home directory
261
+ home_cmd = "echo $HOME"
262
+ home_path = self.ssh_handler.execute_command(home_cmd).strip()
263
+ if home_path:
264
+ self.current_path = home_path
265
+ self.selected_index = 0
266
+ self.scroll_offset = 0
267
+ self.items = self.get_directory_contents()
268
+ self.status_message = f"Navigated to home: {home_path}"
269
+ elif key == ord('r') or key == ord('R'):
270
+ # Refresh
271
+ self.items = self.get_directory_contents()
272
+ self.status_message = "Refreshed"
273
+ elif key == ord('n') or key == ord('N'):
274
+ # Create new folder
275
+ result = self.create_folder(stdscr)
276
+ if result:
277
+ self.status_message = result
278
+ # Refresh directory listing
279
+ self.items = self.get_directory_contents()
280
+ else:
281
+ self.status_message = "Folder creation cancelled"
282
+
283
+
284
+ def main():
285
+ """Main entry point"""
286
+ parser = argparse.ArgumentParser(
287
+ description="SSH Directory Browser - Browse remote directories and open in VS Code",
288
+ formatter_class=argparse.RawDescriptionHelpFormatter,
289
+ epilog="""
290
+ Examples:
291
+ %(prog)s user@hostname
292
+ %(prog)s user@hostname -p 2222
293
+ %(prog)s user@hostname --start-path /var/www
294
+ %(prog)s user@hostname -i ~/.ssh/id_rsa
295
+ """
296
+ )
297
+ parser.add_argument('host', help='SSH host in format user@hostname')
298
+ parser.add_argument('-p', '--port', type=int, default=22, help='SSH port (default: 22)')
299
+ parser.add_argument('-i', '--identity', help='SSH private key file')
300
+ parser.add_argument('--start-path', default='~', help='Starting directory (default: ~)')
301
+ parser.add_argument('--password', action='store_true',
302
+ help='Prompt for password authentication')
303
+
304
+ args = parser.parse_args()
305
+
306
+ # Parse user@host
307
+ if '@' not in args.host:
308
+ print("Error: Host must be in format user@hostname")
309
+ sys.exit(1)
310
+
311
+ username, hostname = args.host.split('@', 1)
312
+
313
+ # Import SSH handler
314
+ try:
315
+ from ssh_handler import SSHHandler
316
+ except ImportError:
317
+ print("Error: ssh_handler module not found")
318
+ print("Make sure ssh_handler.py is in the same directory")
319
+ sys.exit(1)
320
+
321
+ # Connect to SSH
322
+ print(f"Connecting to {username}@{hostname}:{args.port}...")
323
+
324
+ try:
325
+ ssh = SSHHandler(
326
+ hostname=hostname,
327
+ username=username,
328
+ port=args.port,
329
+ key_filename=args.identity,
330
+ use_password=args.password
331
+ )
332
+
333
+ if not ssh.connect():
334
+ print("Failed to connect to SSH server")
335
+ sys.exit(1)
336
+
337
+ print("Connected successfully!")
338
+
339
+ # Resolve start path
340
+ start_path = args.start_path
341
+ if start_path == '~':
342
+ home_cmd = "echo $HOME"
343
+ start_path = ssh.execute_command(home_cmd).strip() or "/"
344
+
345
+ # Start the browser
346
+ browser = DirectoryBrowser(ssh, start_path)
347
+ curses.wrapper(browser.run)
348
+
349
+ # Cleanup
350
+ ssh.disconnect()
351
+ print("\nDisconnected. Goodbye!")
352
+
353
+ except KeyboardInterrupt:
354
+ print("\n\nInterrupted by user")
355
+ sys.exit(0)
356
+ except Exception as e:
357
+ print(f"\nError: {str(e)}")
358
+ sys.exit(1)
359
+
360
+
361
+ if __name__ == "__main__":
362
+ main()