qssh 0.1.0__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.
qssh-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
qssh-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: qssh
3
+ Version: 0.1.0
4
+ Summary: Quick SSH session manager - save your VM credentials and connect with a single command
5
+ Author-email: joan-code6 <your-email@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yourusername/qssh
8
+ Project-URL: Repository, https://github.com/yourusername/qssh
9
+ Project-URL: Issues, https://github.com/yourusername/qssh/issues
10
+ Keywords: ssh,session,manager,vm,cli,terminal
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.0.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: pyyaml>=6.0
31
+ Dynamic: license-file
32
+
33
+ # qssh
34
+
35
+ **Quick SSH session manager** - Save your VM credentials and connect with a single command.
36
+
37
+ Tired of copy-pasting credentials every time you want to SSH into your VMs? `qssh` lets you save your session configs and connect instantly.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install qssh
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### 1. Add a new session
48
+
49
+ ```bash
50
+ qssh add outcraft
51
+ ```
52
+
53
+ You'll be prompted for:
54
+ - Host (IP address or hostname)
55
+ - Username
56
+ - Port (default: 22)
57
+ - Authentication method (password or key file)
58
+
59
+ ### 2. Connect to your VM
60
+
61
+ ```bash
62
+ qssh outcraft
63
+ ```
64
+
65
+ That's it! You're connected.
66
+
67
+ ## Commands
68
+
69
+ | Command | Description |
70
+ |---------|-------------|
71
+ | `qssh <session>` | Connect to a saved session |
72
+ | `qssh add <name>` | Add a new session |
73
+ | `qssh list` | List all saved sessions |
74
+ | `qssh remove <name>` | Remove a session |
75
+ | `qssh edit <name>` | Edit an existing session |
76
+ | `qssh show <name>` | Show session details |
77
+ | `qssh config` | Show config file location |
78
+
79
+ ## Examples
80
+
81
+ ```bash
82
+ # Add a session for your OutCraft VM
83
+ qssh add outcraft
84
+ # Host: 192.168.1.100
85
+ # Username: admin
86
+ # Port [22]: 22
87
+ # Auth type (password/key) [password]: password
88
+ # Password: ********
89
+
90
+ # Now just connect with:
91
+ qssh outcraft
92
+
93
+ # List all your sessions
94
+ qssh list
95
+
96
+ # Remove a session
97
+ qssh remove old-server
98
+
99
+ # Show details of a session
100
+ qssh show outcraft
101
+ ```
102
+
103
+ ## Using SSH Keys
104
+
105
+ For key-based authentication:
106
+
107
+ ```bash
108
+ qssh add myserver
109
+ # Host: example.com
110
+ # Username: deploy
111
+ # Port [22]: 22
112
+ # Auth type (password/key) [password]: key
113
+ # Key file path: ~/.ssh/id_rsa
114
+ ```
115
+
116
+ ## Configuration
117
+
118
+ Sessions are stored in `~/.qssh/sessions.yaml`. Passwords are stored encoded (not plaintext) but for maximum security, consider using SSH keys instead.
119
+
120
+ ## License
121
+
122
+ MIT License
qssh-0.1.0/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # qssh
2
+
3
+ **Quick SSH session manager** - Save your VM credentials and connect with a single command.
4
+
5
+ Tired of copy-pasting credentials every time you want to SSH into your VMs? `qssh` lets you save your session configs and connect instantly.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install qssh
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### 1. Add a new session
16
+
17
+ ```bash
18
+ qssh add outcraft
19
+ ```
20
+
21
+ You'll be prompted for:
22
+ - Host (IP address or hostname)
23
+ - Username
24
+ - Port (default: 22)
25
+ - Authentication method (password or key file)
26
+
27
+ ### 2. Connect to your VM
28
+
29
+ ```bash
30
+ qssh outcraft
31
+ ```
32
+
33
+ That's it! You're connected.
34
+
35
+ ## Commands
36
+
37
+ | Command | Description |
38
+ |---------|-------------|
39
+ | `qssh <session>` | Connect to a saved session |
40
+ | `qssh add <name>` | Add a new session |
41
+ | `qssh list` | List all saved sessions |
42
+ | `qssh remove <name>` | Remove a session |
43
+ | `qssh edit <name>` | Edit an existing session |
44
+ | `qssh show <name>` | Show session details |
45
+ | `qssh config` | Show config file location |
46
+
47
+ ## Examples
48
+
49
+ ```bash
50
+ # Add a session for your OutCraft VM
51
+ qssh add outcraft
52
+ # Host: 192.168.1.100
53
+ # Username: admin
54
+ # Port [22]: 22
55
+ # Auth type (password/key) [password]: password
56
+ # Password: ********
57
+
58
+ # Now just connect with:
59
+ qssh outcraft
60
+
61
+ # List all your sessions
62
+ qssh list
63
+
64
+ # Remove a session
65
+ qssh remove old-server
66
+
67
+ # Show details of a session
68
+ qssh show outcraft
69
+ ```
70
+
71
+ ## Using SSH Keys
72
+
73
+ For key-based authentication:
74
+
75
+ ```bash
76
+ qssh add myserver
77
+ # Host: example.com
78
+ # Username: deploy
79
+ # Port [22]: 22
80
+ # Auth type (password/key) [password]: key
81
+ # Key file path: ~/.ssh/id_rsa
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ Sessions are stored in `~/.qssh/sessions.yaml`. Passwords are stored encoded (not plaintext) but for maximum security, consider using SSH keys instead.
87
+
88
+ ## License
89
+
90
+ MIT License
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "qssh"
7
+ version = "0.1.0"
8
+ description = "Quick SSH session manager - save your VM credentials and connect with a single command"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [
12
+ {name = "joan-code6", email = "your-email@example.com"}
13
+ ]
14
+ keywords = ["ssh", "session", "manager", "vm", "cli", "terminal"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: System Administrators",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: System :: Networking",
29
+ "Topic :: Utilities",
30
+ ]
31
+ requires-python = ">=3.8"
32
+ dependencies = [
33
+ "click>=8.0.0",
34
+ "rich>=13.0.0",
35
+ "pyyaml>=6.0",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/yourusername/qssh"
40
+ Repository = "https://github.com/yourusername/qssh"
41
+ Issues = "https://github.com/yourusername/qssh/issues"
42
+
43
+ [project.scripts]
44
+ qssh = "qssh.cli:main"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.setuptools.package-data]
50
+ qssh = ["py.typed"]
qssh-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ """qssh - Quick SSH session manager."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "benne"
5
+
6
+ from .session import SessionManager
7
+ from .connector import SSHConnector
8
+
9
+ __all__ = ["SessionManager", "SSHConnector", "__version__"]
@@ -0,0 +1,281 @@
1
+ """Command-line interface for qssh."""
2
+
3
+ import sys
4
+ import click
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+ from rich.panel import Panel
8
+ from rich.prompt import Prompt, Confirm
9
+
10
+ from . import __version__
11
+ from .session import Session, SessionManager
12
+ from .connector import SSHConnector
13
+
14
+
15
+ console = Console()
16
+ manager = SessionManager()
17
+ connector = SSHConnector()
18
+
19
+
20
+ @click.group(invoke_without_command=True)
21
+ @click.argument("session_name", required=False)
22
+ @click.option("--version", "-v", is_flag=True, help="Show version")
23
+ @click.pass_context
24
+ def main(ctx, session_name: str, version: bool):
25
+ """qssh - Quick SSH session manager.
26
+
27
+ Connect to a saved session:
28
+
29
+ qssh <session-name>
30
+
31
+ Or use subcommands to manage sessions:
32
+
33
+ qssh add <name> Add a new session
34
+
35
+ qssh list List all sessions
36
+
37
+ qssh remove <name> Remove a session
38
+ """
39
+ if version:
40
+ console.print(f"[bold blue]qssh[/] version [green]{__version__}[/]")
41
+ return
42
+
43
+ if ctx.invoked_subcommand is None:
44
+ if session_name:
45
+ # Try to connect to the session
46
+ _connect(session_name)
47
+ else:
48
+ # Show help
49
+ click.echo(ctx.get_help())
50
+
51
+
52
+ def _connect(name: str) -> None:
53
+ """Connect to a session by name."""
54
+ session = manager.get(name)
55
+
56
+ if not session:
57
+ console.print(f"[red]Session '[bold]{name}[/bold]' not found.[/]")
58
+ console.print("\nAvailable sessions:")
59
+ _list_sessions_simple()
60
+ console.print(f"\n[dim]Use 'qssh add {name}' to create this session.[/]")
61
+ sys.exit(1)
62
+
63
+ console.print(f"[bold blue]Connecting to[/] [green]{name}[/] ({session.username}@{session.host}:{session.port})")
64
+ console.print()
65
+
66
+ exit_code = connector.connect(session)
67
+ sys.exit(exit_code)
68
+
69
+
70
+ def _list_sessions_simple() -> None:
71
+ """List sessions in a simple format."""
72
+ sessions = manager.list_all()
73
+ if not sessions:
74
+ console.print("[dim] No sessions saved yet.[/]")
75
+ return
76
+
77
+ for s in sessions:
78
+ console.print(f" • [cyan]{s.name}[/] → {s.username}@{s.host}")
79
+
80
+
81
+ @main.command("add")
82
+ @click.argument("name")
83
+ def add_session(name: str):
84
+ """Add a new SSH session."""
85
+ if manager.exists(name):
86
+ if not Confirm.ask(f"Session '[bold]{name}[/]' already exists. Overwrite?"):
87
+ console.print("[yellow]Cancelled.[/]")
88
+ return
89
+
90
+ console.print(Panel(f"[bold blue]Adding session:[/] [green]{name}[/]", expand=False))
91
+
92
+ # Gather session info
93
+ host = Prompt.ask("[bold]Host[/] (IP or hostname)")
94
+ username = Prompt.ask("[bold]Username[/]")
95
+ port = Prompt.ask("[bold]Port[/]", default="22")
96
+
97
+ try:
98
+ port = int(port)
99
+ except ValueError:
100
+ console.print("[red]Invalid port number. Using 22.[/]")
101
+ port = 22
102
+
103
+ auth_type = Prompt.ask(
104
+ "[bold]Auth type[/]",
105
+ choices=["password", "key"],
106
+ default="password"
107
+ )
108
+
109
+ password = None
110
+ key_file = None
111
+
112
+ if auth_type == "password":
113
+ password_raw = Prompt.ask("[bold]Password[/]", password=True)
114
+ if password_raw:
115
+ password = Session.encode_password(password_raw)
116
+ else:
117
+ key_file = Prompt.ask(
118
+ "[bold]Key file path[/]",
119
+ default="~/.ssh/id_rsa"
120
+ )
121
+
122
+ # Create and save session
123
+ session = Session(
124
+ name=name,
125
+ host=host,
126
+ username=username,
127
+ port=port,
128
+ auth_type=auth_type,
129
+ password=password,
130
+ key_file=key_file,
131
+ )
132
+
133
+ manager.add(session)
134
+ console.print(f"\n[green]✓[/] Session '[bold]{name}[/]' saved!")
135
+ console.print(f"[dim]Connect with: qssh {name}[/]")
136
+
137
+
138
+ @main.command("list")
139
+ def list_sessions():
140
+ """List all saved sessions."""
141
+ sessions = manager.list_all()
142
+
143
+ if not sessions:
144
+ console.print("[yellow]No sessions saved yet.[/]")
145
+ console.print("[dim]Use 'qssh add <name>' to add one.[/]")
146
+ return
147
+
148
+ table = Table(title="SSH Sessions", show_header=True, header_style="bold blue")
149
+ table.add_column("Name", style="cyan", no_wrap=True)
150
+ table.add_column("Host", style="green")
151
+ table.add_column("User", style="yellow")
152
+ table.add_column("Port", justify="right")
153
+ table.add_column("Auth", style="magenta")
154
+
155
+ for session in sessions:
156
+ auth_display = "🔑 key" if session.auth_type == "key" else "🔒 pass"
157
+ table.add_row(
158
+ session.name,
159
+ session.host,
160
+ session.username,
161
+ str(session.port),
162
+ auth_display,
163
+ )
164
+
165
+ console.print(table)
166
+
167
+
168
+ @main.command("remove")
169
+ @click.argument("name")
170
+ def remove_session(name: str):
171
+ """Remove a saved session."""
172
+ if not manager.exists(name):
173
+ console.print(f"[red]Session '[bold]{name}[/bold]' not found.[/]")
174
+ sys.exit(1)
175
+
176
+ if Confirm.ask(f"Remove session '[bold]{name}[/]'?"):
177
+ manager.remove(name)
178
+ console.print(f"[green]✓[/] Session '[bold]{name}[/]' removed.")
179
+ else:
180
+ console.print("[yellow]Cancelled.[/]")
181
+
182
+
183
+ @main.command("edit")
184
+ @click.argument("name")
185
+ def edit_session(name: str):
186
+ """Edit an existing session."""
187
+ session = manager.get(name)
188
+
189
+ if not session:
190
+ console.print(f"[red]Session '[bold]{name}[/bold]' not found.[/]")
191
+ sys.exit(1)
192
+
193
+ console.print(Panel(f"[bold blue]Editing session:[/] [green]{name}[/]", expand=False))
194
+ console.print("[dim]Press Enter to keep current value.[/]\n")
195
+
196
+ # Gather updated info
197
+ host = Prompt.ask("[bold]Host[/]", default=session.host)
198
+ username = Prompt.ask("[bold]Username[/]", default=session.username)
199
+ port = Prompt.ask("[bold]Port[/]", default=str(session.port))
200
+
201
+ try:
202
+ port = int(port)
203
+ except ValueError:
204
+ port = session.port
205
+
206
+ auth_type = Prompt.ask(
207
+ "[bold]Auth type[/]",
208
+ choices=["password", "key"],
209
+ default=session.auth_type
210
+ )
211
+
212
+ password = session.password
213
+ key_file = session.key_file
214
+
215
+ if auth_type == "password":
216
+ if Confirm.ask("Update password?", default=False):
217
+ password_raw = Prompt.ask("[bold]Password[/]", password=True)
218
+ if password_raw:
219
+ password = Session.encode_password(password_raw)
220
+ key_file = None
221
+ else:
222
+ key_file = Prompt.ask(
223
+ "[bold]Key file path[/]",
224
+ default=session.key_file or "~/.ssh/id_rsa"
225
+ )
226
+ password = None
227
+
228
+ # Update session
229
+ updated = Session(
230
+ name=name,
231
+ host=host,
232
+ username=username,
233
+ port=port,
234
+ auth_type=auth_type,
235
+ password=password,
236
+ key_file=key_file,
237
+ )
238
+
239
+ manager.add(updated)
240
+ console.print(f"\n[green]✓[/] Session '[bold]{name}[/]' updated!")
241
+
242
+
243
+ @main.command("show")
244
+ @click.argument("name")
245
+ def show_session(name: str):
246
+ """Show details of a session."""
247
+ session = manager.get(name)
248
+
249
+ if not session:
250
+ console.print(f"[red]Session '[bold]{name}[/bold]' not found.[/]")
251
+ sys.exit(1)
252
+
253
+ table = Table(show_header=False, box=None, padding=(0, 2))
254
+ table.add_column("Property", style="bold blue")
255
+ table.add_column("Value", style="green")
256
+
257
+ table.add_row("Name", session.name)
258
+ table.add_row("Host", session.host)
259
+ table.add_row("Username", session.username)
260
+ table.add_row("Port", str(session.port))
261
+ table.add_row("Auth Type", session.auth_type)
262
+
263
+ if session.auth_type == "key":
264
+ table.add_row("Key File", session.key_file or "~/.ssh/id_rsa")
265
+ else:
266
+ table.add_row("Password", "••••••••" if session.password else "[dim]not set[/]")
267
+
268
+ console.print(Panel(table, title=f"[bold]Session: {name}[/]", expand=False))
269
+ console.print(f"\n[dim]Connect with: qssh {name}[/]")
270
+
271
+
272
+ @main.command("config")
273
+ def show_config():
274
+ """Show configuration file location."""
275
+ config_path = manager.get_config_path()
276
+ console.print(f"[bold blue]Config directory:[/] [green]{config_path}[/]")
277
+ console.print(f"[bold blue]Sessions file:[/] [green]{config_path / 'sessions.yaml'}[/]")
278
+
279
+
280
+ if __name__ == "__main__":
281
+ main()
@@ -0,0 +1,143 @@
1
+ """SSH connection handler for qssh."""
2
+
3
+ import os
4
+ import sys
5
+ import subprocess
6
+ import platform
7
+ from typing import Optional
8
+
9
+ from .session import Session
10
+
11
+
12
+ class SSHConnector:
13
+ """Handles SSH connections to remote hosts."""
14
+
15
+ def __init__(self):
16
+ """Initialize SSH connector."""
17
+ self.system = platform.system().lower()
18
+
19
+ def connect(self, session: Session) -> int:
20
+ """Connect to a session.
21
+
22
+ Args:
23
+ session: Session to connect to
24
+
25
+ Returns:
26
+ Exit code from SSH process
27
+ """
28
+ if session.auth_type == "key":
29
+ return self._connect_with_key(session)
30
+ else:
31
+ return self._connect_with_password(session)
32
+
33
+ def _connect_with_key(self, session: Session) -> int:
34
+ """Connect using SSH key authentication.
35
+
36
+ Args:
37
+ session: Session configuration
38
+
39
+ Returns:
40
+ Exit code
41
+ """
42
+ key_path = os.path.expanduser(session.key_file) if session.key_file else None
43
+
44
+ cmd = [
45
+ "ssh",
46
+ "-o", "StrictHostKeyChecking=accept-new",
47
+ "-p", str(session.port),
48
+ ]
49
+
50
+ if key_path:
51
+ cmd.extend(["-i", key_path])
52
+
53
+ cmd.append(f"{session.username}@{session.host}")
54
+
55
+ return self._run_ssh(cmd)
56
+
57
+ def _connect_with_password(self, session: Session) -> int:
58
+ """Connect using password authentication.
59
+
60
+ Uses sshpass if available, otherwise prompts for password.
61
+
62
+ Args:
63
+ session: Session configuration
64
+
65
+ Returns:
66
+ Exit code
67
+ """
68
+ password = session.get_password()
69
+
70
+ # Check if sshpass is available for automatic password entry
71
+ if password and self._has_sshpass():
72
+ return self._connect_with_sshpass(session, password)
73
+
74
+ # Fall back to regular SSH (will prompt for password if needed)
75
+ cmd = [
76
+ "ssh",
77
+ "-o", "StrictHostKeyChecking=accept-new",
78
+ "-p", str(session.port),
79
+ f"{session.username}@{session.host}",
80
+ ]
81
+
82
+ if password:
83
+ # If we have a password but no sshpass, inform the user
84
+ print(f"[qssh] Password stored. Copy it or install 'sshpass' for auto-login.")
85
+ print(f"[qssh] Password: {password}")
86
+ print()
87
+
88
+ return self._run_ssh(cmd)
89
+
90
+ def _connect_with_sshpass(self, session: Session, password: str) -> int:
91
+ """Connect using sshpass for automatic password entry.
92
+
93
+ Args:
94
+ session: Session configuration
95
+ password: Decoded password
96
+
97
+ Returns:
98
+ Exit code
99
+ """
100
+ cmd = [
101
+ "sshpass", "-p", password,
102
+ "ssh",
103
+ "-o", "StrictHostKeyChecking=accept-new",
104
+ "-p", str(session.port),
105
+ f"{session.username}@{session.host}",
106
+ ]
107
+
108
+ return self._run_ssh(cmd)
109
+
110
+ def _has_sshpass(self) -> bool:
111
+ """Check if sshpass is available on the system."""
112
+ try:
113
+ subprocess.run(
114
+ ["sshpass", "-V"],
115
+ capture_output=True,
116
+ check=False
117
+ )
118
+ return True
119
+ except FileNotFoundError:
120
+ return False
121
+
122
+ def _run_ssh(self, cmd: list) -> int:
123
+ """Run SSH command and return exit code.
124
+
125
+ Args:
126
+ cmd: Command to run
127
+
128
+ Returns:
129
+ Exit code
130
+ """
131
+ try:
132
+ # Run SSH interactively
133
+ result = subprocess.run(cmd)
134
+ return result.returncode
135
+ except FileNotFoundError:
136
+ print("[qssh] Error: SSH client not found.")
137
+ print("[qssh] Please ensure OpenSSH is installed and in your PATH.")
138
+ if self.system == "windows":
139
+ print("[qssh] On Windows, you can enable it in Settings > Apps > Optional Features")
140
+ return 1
141
+ except KeyboardInterrupt:
142
+ print("\n[qssh] Connection interrupted.")
143
+ return 130
File without changes
@@ -0,0 +1,151 @@
1
+ """Session management for qssh."""
2
+
3
+ import os
4
+ import base64
5
+ from pathlib import Path
6
+ from dataclasses import dataclass, asdict
7
+ from typing import Optional, Dict, List
8
+
9
+ import yaml
10
+
11
+
12
+ @dataclass
13
+ class Session:
14
+ """Represents an SSH session configuration."""
15
+
16
+ name: str
17
+ host: str
18
+ username: str
19
+ port: int = 22
20
+ auth_type: str = "password" # "password" or "key"
21
+ password: Optional[str] = None # base64 encoded
22
+ key_file: Optional[str] = None
23
+
24
+ def to_dict(self) -> dict:
25
+ """Convert session to dictionary for storage."""
26
+ data = asdict(self)
27
+ # Don't store None values
28
+ return {k: v for k, v in data.items() if v is not None}
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: dict) -> "Session":
32
+ """Create session from dictionary."""
33
+ return cls(**data)
34
+
35
+ def get_password(self) -> Optional[str]:
36
+ """Decode and return the password."""
37
+ if self.password:
38
+ try:
39
+ return base64.b64decode(self.password.encode()).decode()
40
+ except Exception:
41
+ return self.password
42
+ return None
43
+
44
+ @staticmethod
45
+ def encode_password(password: str) -> str:
46
+ """Encode password for storage."""
47
+ return base64.b64encode(password.encode()).decode()
48
+
49
+
50
+ class SessionManager:
51
+ """Manages SSH sessions storage and retrieval."""
52
+
53
+ def __init__(self, config_dir: Optional[Path] = None):
54
+ """Initialize session manager.
55
+
56
+ Args:
57
+ config_dir: Custom config directory. Defaults to ~/.qssh
58
+ """
59
+ if config_dir is None:
60
+ config_dir = Path.home() / ".qssh"
61
+
62
+ self.config_dir = config_dir
63
+ self.sessions_file = config_dir / "sessions.yaml"
64
+ self._ensure_config_dir()
65
+
66
+ def _ensure_config_dir(self) -> None:
67
+ """Create config directory if it doesn't exist."""
68
+ self.config_dir.mkdir(parents=True, exist_ok=True)
69
+
70
+ # Create sessions file if it doesn't exist
71
+ if not self.sessions_file.exists():
72
+ self._save_sessions({})
73
+
74
+ def _load_sessions(self) -> Dict[str, dict]:
75
+ """Load sessions from file."""
76
+ if not self.sessions_file.exists():
77
+ return {}
78
+
79
+ with open(self.sessions_file, "r", encoding="utf-8") as f:
80
+ data = yaml.safe_load(f)
81
+ return data if data else {}
82
+
83
+ def _save_sessions(self, sessions: Dict[str, dict]) -> None:
84
+ """Save sessions to file."""
85
+ with open(self.sessions_file, "w", encoding="utf-8") as f:
86
+ yaml.dump(sessions, f, default_flow_style=False, sort_keys=False)
87
+
88
+ def add(self, session: Session) -> None:
89
+ """Add or update a session.
90
+
91
+ Args:
92
+ session: Session to add
93
+ """
94
+ sessions = self._load_sessions()
95
+ sessions[session.name] = session.to_dict()
96
+ self._save_sessions(sessions)
97
+
98
+ def get(self, name: str) -> Optional[Session]:
99
+ """Get a session by name.
100
+
101
+ Args:
102
+ name: Session name
103
+
104
+ Returns:
105
+ Session if found, None otherwise
106
+ """
107
+ sessions = self._load_sessions()
108
+ if name in sessions:
109
+ return Session.from_dict(sessions[name])
110
+ return None
111
+
112
+ def remove(self, name: str) -> bool:
113
+ """Remove a session.
114
+
115
+ Args:
116
+ name: Session name
117
+
118
+ Returns:
119
+ True if removed, False if not found
120
+ """
121
+ sessions = self._load_sessions()
122
+ if name in sessions:
123
+ del sessions[name]
124
+ self._save_sessions(sessions)
125
+ return True
126
+ return False
127
+
128
+ def list_all(self) -> List[Session]:
129
+ """List all sessions.
130
+
131
+ Returns:
132
+ List of all sessions
133
+ """
134
+ sessions = self._load_sessions()
135
+ return [Session.from_dict(data) for data in sessions.values()]
136
+
137
+ def exists(self, name: str) -> bool:
138
+ """Check if session exists.
139
+
140
+ Args:
141
+ name: Session name
142
+
143
+ Returns:
144
+ True if exists
145
+ """
146
+ sessions = self._load_sessions()
147
+ return name in sessions
148
+
149
+ def get_config_path(self) -> Path:
150
+ """Get the config directory path."""
151
+ return self.config_dir
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: qssh
3
+ Version: 0.1.0
4
+ Summary: Quick SSH session manager - save your VM credentials and connect with a single command
5
+ Author-email: joan-code6 <your-email@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yourusername/qssh
8
+ Project-URL: Repository, https://github.com/yourusername/qssh
9
+ Project-URL: Issues, https://github.com/yourusername/qssh/issues
10
+ Keywords: ssh,session,manager,vm,cli,terminal
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.0.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: pyyaml>=6.0
31
+ Dynamic: license-file
32
+
33
+ # qssh
34
+
35
+ **Quick SSH session manager** - Save your VM credentials and connect with a single command.
36
+
37
+ Tired of copy-pasting credentials every time you want to SSH into your VMs? `qssh` lets you save your session configs and connect instantly.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install qssh
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### 1. Add a new session
48
+
49
+ ```bash
50
+ qssh add outcraft
51
+ ```
52
+
53
+ You'll be prompted for:
54
+ - Host (IP address or hostname)
55
+ - Username
56
+ - Port (default: 22)
57
+ - Authentication method (password or key file)
58
+
59
+ ### 2. Connect to your VM
60
+
61
+ ```bash
62
+ qssh outcraft
63
+ ```
64
+
65
+ That's it! You're connected.
66
+
67
+ ## Commands
68
+
69
+ | Command | Description |
70
+ |---------|-------------|
71
+ | `qssh <session>` | Connect to a saved session |
72
+ | `qssh add <name>` | Add a new session |
73
+ | `qssh list` | List all saved sessions |
74
+ | `qssh remove <name>` | Remove a session |
75
+ | `qssh edit <name>` | Edit an existing session |
76
+ | `qssh show <name>` | Show session details |
77
+ | `qssh config` | Show config file location |
78
+
79
+ ## Examples
80
+
81
+ ```bash
82
+ # Add a session for your OutCraft VM
83
+ qssh add outcraft
84
+ # Host: 192.168.1.100
85
+ # Username: admin
86
+ # Port [22]: 22
87
+ # Auth type (password/key) [password]: password
88
+ # Password: ********
89
+
90
+ # Now just connect with:
91
+ qssh outcraft
92
+
93
+ # List all your sessions
94
+ qssh list
95
+
96
+ # Remove a session
97
+ qssh remove old-server
98
+
99
+ # Show details of a session
100
+ qssh show outcraft
101
+ ```
102
+
103
+ ## Using SSH Keys
104
+
105
+ For key-based authentication:
106
+
107
+ ```bash
108
+ qssh add myserver
109
+ # Host: example.com
110
+ # Username: deploy
111
+ # Port [22]: 22
112
+ # Auth type (password/key) [password]: key
113
+ # Key file path: ~/.ssh/id_rsa
114
+ ```
115
+
116
+ ## Configuration
117
+
118
+ Sessions are stored in `~/.qssh/sessions.yaml`. Passwords are stored encoded (not plaintext) but for maximum security, consider using SSH keys instead.
119
+
120
+ ## License
121
+
122
+ MIT License
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/qssh/__init__.py
5
+ src/qssh/cli.py
6
+ src/qssh/connector.py
7
+ src/qssh/py.typed
8
+ src/qssh/session.py
9
+ src/qssh.egg-info/PKG-INFO
10
+ src/qssh.egg-info/SOURCES.txt
11
+ src/qssh.egg-info/dependency_links.txt
12
+ src/qssh.egg-info/entry_points.txt
13
+ src/qssh.egg-info/requires.txt
14
+ src/qssh.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ qssh = qssh.cli:main
@@ -0,0 +1,3 @@
1
+ click>=8.0.0
2
+ rich>=13.0.0
3
+ pyyaml>=6.0
@@ -0,0 +1 @@
1
+ qssh