ssh-tunnel-cli 0.1.1__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.
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ .pytest_cache/
6
+ .claude/
7
+ .uv-cache/
8
+ .DS_Store
9
+ .env
10
+ .env.*
11
+ .tunnel/
12
+ *.egg-info/
13
+ node_modules/
14
+ *.tgz
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maneesh
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.
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.5
2
+ Name: ssh-tunnel-cli
3
+ Version: 0.1.1
4
+ Summary: Guided local SSH tunnels using your existing OpenSSH configuration
5
+ Project-URL: Repository, https://github.com/maneeshv/ssh-tunnel-cli
6
+ Project-URL: Issues, https://github.com/maneeshv/ssh-tunnel-cli/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Operating System :: MacOS :: MacOS X
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Internet
14
+ Requires-Python: >=3.11
15
+ Requires-Dist: prompt-toolkit==3.0.53
16
+ Requires-Dist: questionary==2.1.1
17
+ Description-Content-Type: text/markdown
18
+
19
+ # tunnel
20
+
21
+ Create and manage local SSH tunnels without remembering forwarding syntax. Pick an alias from your SSH config, answer the prompts, review the exact commands, and choose **Start tunnel**.
22
+
23
+ Python + Questionary, using your Mac's existing OpenSSH client for connections and authentication. No remote agent or installed manager service.
24
+
25
+ ## Install
26
+
27
+ Requires macOS, Python 3.11 or later, [uv](https://docs.astral.sh/uv/), and `/usr/bin/ssh`.
28
+
29
+ ```sh
30
+ uv tool install ssh-tunnel-cli
31
+ tunnel
32
+ ```
33
+
34
+ Alternatively, use `pipx install ssh-tunnel-cli`, or `python -m pip install ssh-tunnel-cli` inside a virtual environment. The Python package exposes the same `tunnel` command.
35
+
36
+ If your shell cannot find `tunnel`, run `uv tool update-shell` and restart the shell.
37
+
38
+ To run or develop from a checkout:
39
+
40
+ ```sh
41
+ git clone https://github.com/maneeshv/ssh-tunnel-cli.git
42
+ cd ssh-tunnel-cli
43
+ uv sync --locked
44
+ uv run tunnel
45
+ ```
46
+
47
+ From that checkout, `uv tool install .` installs the command for everyday use.
48
+
49
+ Your config must already contain a literal alias, for example:
50
+
51
+ ```sshconfig
52
+ Host other-mac
53
+ HostName your-other-machine
54
+ User your-username
55
+ ```
56
+
57
+ Configure and verify SSH access yourself first. `tunnel` does not edit SSH config, set up Tailscale, or save credentials. Add hosts to your config manually and reload the picker.
58
+
59
+ ## Use
60
+
61
+ - **New tunnel:** machine → remote app port → local port → destination → mode → review.
62
+ - **Background:** returns to the shell; SSH keeps running after that terminal closes.
63
+ - **Foreground:** remains attached; Ctrl+C stops this tunnel.
64
+ - **Manage tunnels:** inspect commands and diagnostics, stop, or review a restart.
65
+ - **Recent tunnels:** reuse successful settings, with a fresh review before connecting.
66
+ - **Esc:** back, retaining answers. **Ctrl+C:** cancel. Arrow keys select; type to filter the host picker.
67
+
68
+ ```sh
69
+ tunnel list
70
+ tunnel stop
71
+ tunnel restart
72
+ tunnel --help
73
+ ```
74
+
75
+ The app port is different from the SSH server port. SSH's server port comes from your configuration. The destination `127.0.0.1` means loopback **on the selected remote machine**; the local listener is always `127.0.0.1` **on this Mac**. No public/LAN bind option is provided.
76
+
77
+ **Running means SSH is active, not that the remote app is healthy.** Browser opening is optional and only appropriate for web apps. Tailscale is optional existing connectivity, not something this utility configures or guarantees.
78
+
79
+ ## Connection ownership
80
+
81
+ Every attempt has its own private control socket. The reviewed launch has two commands: first start an isolated SSH connection with inherited forwards cleared, then add the one selected local forward through that socket. This avoids accidentally launching forwards defined elsewhere in SSH configuration. SSH continues to resolve keys, users, jump hosts, and authentication settings. LocalCommand, agent/X11 forwarding, tunnel devices, and persistent/shared control settings are overridden for this utility.
82
+
83
+ Only tool-owned connections appear in the inventory. Stop never searches by port or kills an arbitrary PID. Closing the menu does not stop a background tunnel. Network failures require a manual restart; no auto-start or reconnect service is installed.
84
+
85
+ Native SSH authentication and host-key prompts appear only after Start. User SSH config is trusted: connection configuration such as ProxyCommand or Match exec can execute its configured commands at connection time. Host discovery itself does not execute those directives.
86
+
87
+ ## State and limitations
88
+
89
+ Records, a lock file, SSH control sockets, and diagnostics live in `~/.tunnel/`, with owner-only permissions. Override the location with `--state-dir`; use `--config` for a separate SSH config. A short state path is required by Unix control-socket limits. An unsafe or corrupt state store is reported and preserved, not silently repaired or discarded.
90
+
91
+ Host discovery supports literal ASCII aliases and ordinary nested/globbed Includes. Conditional Includes and paths containing SSH tokens or environment expansions are skipped with a warning that the list may be incomplete. Wildcard/negated Host patterns are not selectable machines. Relative user Includes resolve under `~/.ssh`, including when `--config` points elsewhere, matching OpenSSH.
92
+
93
+ Diagnostics contain SSH errors, not remote application logs or typed authentication input. Each log is trimmed to its last 64 KiB when viewed. Without a background manager, logs can grow between views. Old records and diagnostics remain in the private state directory; no automatic history deletion is performed.
94
+
95
+ Foreground/background lifecycle is macOS-first. Other operating systems and every OpenSSH configuration combination are not promised.
96
+
97
+ ## Verify
98
+
99
+ Unit and prompt tests (no real hosts):
100
+
101
+ ```sh
102
+ uv run python -m unittest discover -s tests -v
103
+ ```
104
+
105
+ Opt-in integration tests start an isolated loopback `sshd`, generate disposable keys, and exercise actual forwarding. They do not read your SSH keys or configuration, change system SSH settings, or contact remote machines. They require permission to bind/connect loopback ports, `/usr/sbin/sshd`, `/usr/bin/ssh-keygen`, and `trash` for fixture cleanup.
106
+
107
+ ```sh
108
+ TUNNEL_SSH_INTEGRATION=1 uv run python -m unittest tests.test_ssh_integration -v
109
+ ```
110
+
111
+ See [the specification](https://github.com/maneeshv/ssh-tunnel-cli/blob/main/SPEC.md) for requirements, decisions, and the [original Warp mockup](https://github.com/maneeshv/ssh-tunnel-cli/blob/main/design/tunnel-mockup.png). The mockup is a design reference, not a screenshot of the implemented terminal UI.
112
+
113
+ Verified on 2026-11-04 with Python 3.11.11 and macOS OpenSSH 10.3p1: all 73 tests passed, including 12 real-SSH integration tests. The wheel and source archive build successfully, and the wheel's CLI runs independently of the source checkout. Menus were inspected in a 96-column terminal with color disabled. Manual Warp rendering and password/passphrase authentication were not checked; generated-key authentication and native host-key confirmation were exercised through a PTY.
114
+
115
+ ## TypeScript comparison
116
+
117
+ A separate [TypeScript alternative](https://github.com/maneeshv/ssh-tunnel-cli/tree/main/typescript-version) is available for comparison. Run `npm ci && npm run build && npm start` from `typescript-version/`. It uses the `tunnel-ts` command and its own state directory; npm publication is pending evaluation.
118
+
119
+ ## Contributing
120
+
121
+ Small fixes and improvements are welcome. Open an issue for substantial behavior changes before implementing them. Use absolute imports, keep dependencies minimal, and run the checks above before submitting a pull request. Include a regression test for behavior changes. CI runs unit/prompt tests and package smoke checks on macOS; real-SSH tests remain opt-in.
122
+
123
+ When reporting a bug, include the macOS, Python, and OpenSSH versions and sanitized reproduction steps. Remove hostnames, usernames, addresses, credentials, and private configuration from pasted diagnostics.
124
+
125
+ ## License
126
+
127
+ [MIT](https://github.com/maneeshv/ssh-tunnel-cli/blob/main/LICENSE). The mockup is an AI-generated design reference; the project is not affiliated with Warp.
@@ -0,0 +1,109 @@
1
+ # tunnel
2
+
3
+ Create and manage local SSH tunnels without remembering forwarding syntax. Pick an alias from your SSH config, answer the prompts, review the exact commands, and choose **Start tunnel**.
4
+
5
+ Python + Questionary, using your Mac's existing OpenSSH client for connections and authentication. No remote agent or installed manager service.
6
+
7
+ ## Install
8
+
9
+ Requires macOS, Python 3.11 or later, [uv](https://docs.astral.sh/uv/), and `/usr/bin/ssh`.
10
+
11
+ ```sh
12
+ uv tool install ssh-tunnel-cli
13
+ tunnel
14
+ ```
15
+
16
+ Alternatively, use `pipx install ssh-tunnel-cli`, or `python -m pip install ssh-tunnel-cli` inside a virtual environment. The Python package exposes the same `tunnel` command.
17
+
18
+ If your shell cannot find `tunnel`, run `uv tool update-shell` and restart the shell.
19
+
20
+ To run or develop from a checkout:
21
+
22
+ ```sh
23
+ git clone https://github.com/maneeshv/ssh-tunnel-cli.git
24
+ cd ssh-tunnel-cli
25
+ uv sync --locked
26
+ uv run tunnel
27
+ ```
28
+
29
+ From that checkout, `uv tool install .` installs the command for everyday use.
30
+
31
+ Your config must already contain a literal alias, for example:
32
+
33
+ ```sshconfig
34
+ Host other-mac
35
+ HostName your-other-machine
36
+ User your-username
37
+ ```
38
+
39
+ Configure and verify SSH access yourself first. `tunnel` does not edit SSH config, set up Tailscale, or save credentials. Add hosts to your config manually and reload the picker.
40
+
41
+ ## Use
42
+
43
+ - **New tunnel:** machine → remote app port → local port → destination → mode → review.
44
+ - **Background:** returns to the shell; SSH keeps running after that terminal closes.
45
+ - **Foreground:** remains attached; Ctrl+C stops this tunnel.
46
+ - **Manage tunnels:** inspect commands and diagnostics, stop, or review a restart.
47
+ - **Recent tunnels:** reuse successful settings, with a fresh review before connecting.
48
+ - **Esc:** back, retaining answers. **Ctrl+C:** cancel. Arrow keys select; type to filter the host picker.
49
+
50
+ ```sh
51
+ tunnel list
52
+ tunnel stop
53
+ tunnel restart
54
+ tunnel --help
55
+ ```
56
+
57
+ The app port is different from the SSH server port. SSH's server port comes from your configuration. The destination `127.0.0.1` means loopback **on the selected remote machine**; the local listener is always `127.0.0.1` **on this Mac**. No public/LAN bind option is provided.
58
+
59
+ **Running means SSH is active, not that the remote app is healthy.** Browser opening is optional and only appropriate for web apps. Tailscale is optional existing connectivity, not something this utility configures or guarantees.
60
+
61
+ ## Connection ownership
62
+
63
+ Every attempt has its own private control socket. The reviewed launch has two commands: first start an isolated SSH connection with inherited forwards cleared, then add the one selected local forward through that socket. This avoids accidentally launching forwards defined elsewhere in SSH configuration. SSH continues to resolve keys, users, jump hosts, and authentication settings. LocalCommand, agent/X11 forwarding, tunnel devices, and persistent/shared control settings are overridden for this utility.
64
+
65
+ Only tool-owned connections appear in the inventory. Stop never searches by port or kills an arbitrary PID. Closing the menu does not stop a background tunnel. Network failures require a manual restart; no auto-start or reconnect service is installed.
66
+
67
+ Native SSH authentication and host-key prompts appear only after Start. User SSH config is trusted: connection configuration such as ProxyCommand or Match exec can execute its configured commands at connection time. Host discovery itself does not execute those directives.
68
+
69
+ ## State and limitations
70
+
71
+ Records, a lock file, SSH control sockets, and diagnostics live in `~/.tunnel/`, with owner-only permissions. Override the location with `--state-dir`; use `--config` for a separate SSH config. A short state path is required by Unix control-socket limits. An unsafe or corrupt state store is reported and preserved, not silently repaired or discarded.
72
+
73
+ Host discovery supports literal ASCII aliases and ordinary nested/globbed Includes. Conditional Includes and paths containing SSH tokens or environment expansions are skipped with a warning that the list may be incomplete. Wildcard/negated Host patterns are not selectable machines. Relative user Includes resolve under `~/.ssh`, including when `--config` points elsewhere, matching OpenSSH.
74
+
75
+ Diagnostics contain SSH errors, not remote application logs or typed authentication input. Each log is trimmed to its last 64 KiB when viewed. Without a background manager, logs can grow between views. Old records and diagnostics remain in the private state directory; no automatic history deletion is performed.
76
+
77
+ Foreground/background lifecycle is macOS-first. Other operating systems and every OpenSSH configuration combination are not promised.
78
+
79
+ ## Verify
80
+
81
+ Unit and prompt tests (no real hosts):
82
+
83
+ ```sh
84
+ uv run python -m unittest discover -s tests -v
85
+ ```
86
+
87
+ Opt-in integration tests start an isolated loopback `sshd`, generate disposable keys, and exercise actual forwarding. They do not read your SSH keys or configuration, change system SSH settings, or contact remote machines. They require permission to bind/connect loopback ports, `/usr/sbin/sshd`, `/usr/bin/ssh-keygen`, and `trash` for fixture cleanup.
88
+
89
+ ```sh
90
+ TUNNEL_SSH_INTEGRATION=1 uv run python -m unittest tests.test_ssh_integration -v
91
+ ```
92
+
93
+ See [the specification](https://github.com/maneeshv/ssh-tunnel-cli/blob/main/SPEC.md) for requirements, decisions, and the [original Warp mockup](https://github.com/maneeshv/ssh-tunnel-cli/blob/main/design/tunnel-mockup.png). The mockup is a design reference, not a screenshot of the implemented terminal UI.
94
+
95
+ Verified on 2026-11-04 with Python 3.11.11 and macOS OpenSSH 10.3p1: all 73 tests passed, including 12 real-SSH integration tests. The wheel and source archive build successfully, and the wheel's CLI runs independently of the source checkout. Menus were inspected in a 96-column terminal with color disabled. Manual Warp rendering and password/passphrase authentication were not checked; generated-key authentication and native host-key confirmation were exercised through a PTY.
96
+
97
+ ## TypeScript comparison
98
+
99
+ A separate [TypeScript alternative](https://github.com/maneeshv/ssh-tunnel-cli/tree/main/typescript-version) is available for comparison. Run `npm ci && npm run build && npm start` from `typescript-version/`. It uses the `tunnel-ts` command and its own state directory; npm publication is pending evaluation.
100
+
101
+ ## Contributing
102
+
103
+ Small fixes and improvements are welcome. Open an issue for substantial behavior changes before implementing them. Use absolute imports, keep dependencies minimal, and run the checks above before submitting a pull request. Include a regression test for behavior changes. CI runs unit/prompt tests and package smoke checks on macOS; real-SSH tests remain opt-in.
104
+
105
+ When reporting a bug, include the macOS, Python, and OpenSSH versions and sanitized reproduction steps. Remove hostnames, usernames, addresses, credentials, and private configuration from pasted diagnostics.
106
+
107
+ ## License
108
+
109
+ [MIT](https://github.com/maneeshv/ssh-tunnel-cli/blob/main/LICENSE). The mockup is an AI-generated design reference; the project is not affiliated with Warp.
@@ -0,0 +1,245 @@
1
+ # tunnel — interactive SSH tunnel CLI
2
+
3
+ Status: v0.1.0 implementation baseline, 2026-11-04. This document specifies a standalone terminal utility. See [README.md](README.md) for installation, usage, and verification.
4
+
5
+ ## 1. What we are building
6
+
7
+ A small terminal utility that makes a remote development application available on this Mac without memorizing an SSH command. Run `tunnel`, select an existing SSH-config alias, answer one question at a time, review the connection, and explicitly start it. Return later to inspect, stop, or restart tunnels created by the utility.
8
+
9
+ The motivating workflow is repeatedly accessing applications developed on another machine, with SSH connectivity already available. For example:
10
+
11
+ ```sh
12
+ ssh -N -L 8790:127.0.0.1:8790 other-mac
13
+ ```
14
+
15
+ The utility replaces remembering the forwarding syntax. It does not configure Tailscale, install a remote agent, or change the remote application. It runs on the machine where the local port and browser are needed. Tailscale is the user's existing network transport, not a required integration or proof that every configured host uses the tailnet.
16
+
17
+ Success means a first-time user can create a tunnel entirely through prompts, and a returning user can reuse or manage it without reconstructing the command.
18
+
19
+ ## 2. Requirements and decisions
20
+
21
+ - Discover hosts from existing SSH configuration; users add machines to that configuration themselves.
22
+ - Ask one question at a time, with arrow-key selection and searchable host aliases.
23
+ - Show the exact commands and require confirmation before connecting.
24
+ - Support foreground and background execution, then list and manage owned tunnels.
25
+ - Keep recent successful settings and SSH diagnostics; browser opening is explicit.
26
+
27
+ The implementation uses Python + Questionary, system OpenSSH, and uv. The command is `tunnel`, background mode is the default, and local listeners bind only to `127.0.0.1`. A detached SSH process provides background operation; there is no persistent manager service.
28
+
29
+ The design originated in an interactive terminal workflow and a Warp-based mockup. The image below is a visual reference; the implemented behavior described here takes precedence where the image differs.
30
+
31
+ ## 3. Original visual mockup
32
+
33
+ ![Original Warp-based design board showing host selection, connection details, review and confirmation, tunnel management, and connecting/running/port-occupied states](design/tunnel-mockup.png)
34
+
35
+ [Open the full-resolution mockup](design/tunnel-mockup.png) · [Original generation prompt](design/tunnel-mockup-prompt.txt)
36
+
37
+ This is an AI-generated design board, not a screenshot of the implemented CLI. Example aliases are illustrative, not discovered hosts. Cyan numbered labels are review annotations outside the product. Warp's wallpaper, toolbar, and window chrome belong to the terminal; the CLI renders only terminal text and prompts. The lower panels show separate moments, not simultaneous dashboard panes.
38
+
39
+ Interpret the board with these corrections:
40
+
41
+ - `New tunnel · 1 of 5` counts the five input stages only. Review is an additional stage. Prefer no numeric counter until the branching flow is implemented.
42
+ - The image uses **Show command** on demand; the earlier prose proposed displaying the exact command on the review screen. The implementation always shows the exact commands before Start.
43
+ - Ports are separate prompts even though the connection-details panel summarizes them together.
44
+ - Management selects a tunnel first, then displays its available actions.
45
+ - The example `http://127.0.0.1:8790` assumes an HTTP app. A TCP tunnel alone does not establish a protocol or a healthy application.
46
+
47
+ ## 4. Version-one scope
48
+
49
+ | Included in v0.1.0 | Deferred |
50
+ |---|---|
51
+ | Local TCP forwarding through an existing SSH alias | Remote forwarding, SOCKS, UDP, Unix socket forwarding |
52
+ | Searchable keyboard-driven host selection | Editing SSH config or entering arbitrary SSH login details |
53
+ | Remote port, local port, and destination prompts | Service discovery and automatic remote port scanning |
54
+ | Foreground or detached SSH process | Installed daemon, startup service, automatic reconnect |
55
+ | Review, edit, cancel, and explicit start | One-click connection without confirmation |
56
+ | Owned-tunnel inventory, details, logs, stop, restart | Adopting or killing unrelated SSH sessions |
57
+ | Reuse of recent successful settings | Cloud accounts, synchronization, telemetry |
58
+ | macOS first; Warp is the visual reference | Guaranteed Linux/Windows support or a full-screen TUI |
59
+
60
+ One record represents one local forwarding route. Multiple routes can run as separate tunnels. The first version needs no server, database service, or SSH protocol implementation.
61
+
62
+ ## 5. Interaction specification
63
+
64
+ ### Entry point
65
+
66
+ ```text
67
+ $ tunnel
68
+
69
+ What would you like to do?
70
+ ❯ New tunnel
71
+ Manage tunnels · 2 running
72
+ Recent tunnels
73
+ Exit
74
+
75
+ ↑ ↓ navigate Enter select Ctrl+C cancel
76
+ ```
77
+
78
+ The running count is refreshed from owned connections, not trusted from saved state. Menus must work without color, keep the selected row identifiable, and avoid truncating meaningful port or destination information. Use short prompts and wrapping at ordinary terminal widths. No mouse interaction is required.
79
+
80
+ ### Create a tunnel
81
+
82
+ | Stage | Prompt and behavior |
83
+ |---|---|
84
+ | Machine | Reload explicit aliases from SSH config. Type to filter; arrows navigate; Enter chooses an existing alias. No arbitrary host submission. |
85
+ | Remote port | Ask for the application's listening port, e.g. `8790`. Explain that this is not the SSH server port, which comes from SSH configuration. |
86
+ | Local port | Default to the remote port. Check availability; if occupied, keep the answer editable and offer a different port. Never silently change the route. |
87
+ | Destination | Default to `127.0.0.1` on the selected machine. Offer an advanced hostname/IP reachable from that machine. Explain whose loopback address this is. |
88
+ | Run mode | Background selected by default; Foreground available. State the lifetime and stop behavior before selection. |
89
+ | Review | Show alias, local endpoint, remote destination, mode, and access to the exact executable command. Offer Start tunnel, Edit settings, and Cancel. |
90
+
91
+ Review layout:
92
+
93
+ ```text
94
+ Ready to start
95
+
96
+ Machine other-mac
97
+ Local 127.0.0.1:8790
98
+ Destination 127.0.0.1:8790 on other-mac
99
+ Mode Background
100
+
101
+ Command [exact generated SSH command, wrapped as needed]
102
+
103
+ ❯ Start tunnel
104
+ Edit settings
105
+ Cancel
106
+ ```
107
+
108
+ The placeholder describes the design requirement. The implementation displays two exact invocations: an isolated SSH master with inherited forwards cleared, then the reviewed route added through its private control socket. This sequence was verified against an isolated macOS OpenSSH server. Do not display a simplified command that differs from execution.
109
+
110
+ Esc goes back and preserves answers; Ctrl+C cancels setup cleanly. At the main menu, cancellation exits. Editing an answer returns to review. No tunnel starts during selection, validation, command preview, or editing. Only selecting Start permits connection/authentication.
111
+
112
+ ### Start and finish
113
+
114
+ Show `Connecting to <alias>…` and allow native SSH authentication and host-key prompts on the terminal. Do not report Running until SSH has established the requested local listener and management checks succeed. On failure, show an actionable reason and offer edit/retry/back; do not leave a successful-looking record.
115
+
116
+ In background mode, return control to the shell after confirmation of startup. Closing that terminal must not stop the SSH process. No restart after logout/reboot or reconnection after network failure is promised.
117
+
118
+ In foreground mode, remain attached and show `Ctrl+C stops this tunnel`. Ctrl+C must stop only that tunnel, update its record, and return to the shell. Closing the attached terminal ends the foreground session. Ctrl+C provides an explicit terminal stop gesture.
119
+
120
+ Show the local endpoint after success. Offer browser opening only as an explicit user action for a web application; never assume every TCP destination is HTTP or claim the app is healthy.
121
+
122
+ ### Manage tunnels
123
+
124
+ `tunnel list` prints an inventory without opening an interactive picker. `tunnel` → Manage tunnels provides selection and actions. Optional `tunnel stop` and `tunnel restart` shortcuts may open the same picker; they are not needed to complete the primary flow.
125
+
126
+ | Action | Behavior |
127
+ |---|---|
128
+ | Details | Show stable tunnel ID, alias, endpoints, mode, creation time, observed status, and latest failure when available. |
129
+ | Show command | Show the launch command associated with the record; distinguish it from a newly generated restart command if config has changed. |
130
+ | View logs | Show available SSH diagnostics. Explain when no diagnostics exist. Do not imply application logs are collected. |
131
+ | Stop | Confirm the selected tunnel, stop it through its private control channel, then verify it has stopped. Never select another process based only on port number or PID. |
132
+ | Restart | Review the route and reconfirm. If currently running, stop and verify before starting a fresh instance; make the interruption clear. |
133
+ | Back | Return without changing a connection. |
134
+
135
+ ### Recent tunnels
136
+
137
+ Remember successful connection settings without credentials. Selecting a recent entry pre-fills the wizard/review; it never connects immediately. Recheck that the alias still exists, the port is usable, and the saved inputs are valid. Reuse requires confirmation. An already-running equivalent route should lead to its management entry, not a duplicate listener.
138
+
139
+ ## 6. Host discovery and validation
140
+
141
+ Discovery reads the user's SSH config and supported included files to find literal `Host` aliases. Deduplicate while preserving understandable ordering. A block can contain multiple aliases. Do not present wildcard or negated patterns as actual machines. Global wildcard settings still apply when native SSH connects.
142
+
143
+ The discovery reader is not an SSH configuration engine. OpenSSH remains responsible for connection settings, keys, users, SSH ports, jump hosts, and conditional configuration. Relative includes, quoting, glob expansion, nested includes, and cycles need fixtures. If a conditional or dynamic include cannot be enumerated reliably, explain the discovery limitation rather than claiming a complete host list. Picker discovery must not execute `Match exec` or other configured commands. The upstream [SSH configuration manual](https://man.openbsd.org/ssh_config) is the reference for syntax.
144
+
145
+ Validation requirements:
146
+
147
+ - Ports are decimal integers from 1 to 65535. Do not escalate privileges automatically for a bind failure.
148
+ - Destination is a supported hostname or IP address, not a shell fragment. Correct IPv6 formatting must be tested before advertising IPv6 destinations.
149
+ - The local listener is fixed to `127.0.0.1` in v1; no LAN/public bind option.
150
+ - Use an argument array for process execution, never a shell-interpolated command. Render a safely quoted representation separately for display.
151
+ - Revalidate before launch; a preflight port check cannot eliminate the race with another process binding that port.
152
+ - Do not read key contents, save passwords, replace known-host verification, or rewrite SSH config.
153
+
154
+ ## 7. Lifecycle and ownership contract
155
+
156
+ Each managed tunnel receives a unique short control-socket path under a private user-owned directory. It must not join a pre-existing multiplexed session or inherit a shared control path. Stop/check operations target only that owned socket; no broad process search or PID-only kill fallback.
157
+
158
+ The intended primitive is system OpenSSH: local forwarding, foreground operation or backgrounding after authentication, and control-channel checks/exit. The official [ssh manual](https://man.openbsd.org/ssh) documents these facilities. The exact invocation must be tested against the installed macOS client before implementation is considered complete.
159
+
160
+ Startup must create only the reviewed forwarding route. Existing SSH config may specify other forwards or lifecycle settings; the implementation must detect conflicts and either safely isolate the requested route while preserving authentication/config resolution, or explain the unsupported configuration before connecting. Silently adding inherited forwards is unacceptable. Do not assume a forwarding-clearing option preserves a newly supplied forward without testing it.
161
+
162
+ | Display state | Meaning |
163
+ |---|---|
164
+ | Connecting | Startup is in progress; not yet a usable managed tunnel. |
165
+ | Running | Owned SSH control check succeeds and startup established the requested local forward. Application health remains unknown. |
166
+ | Stopped | The user explicitly stopped the tunnel and shutdown was verified. |
167
+ | Disconnected | A previously running tunnel no longer responds through its owned control channel. Show the last known failure if available; do not invent a cause. |
168
+ | Failed | The attempted launch did not complete successfully. Show diagnostics and retry/edit options. |
169
+ | Unknown | Permissions or another inspection error prevent a trustworthy determination. Do not label it Running or kill anything. |
170
+
171
+ Refresh on entering management and after each action. Stale saved state and socket files are not evidence of a running tunnel. Sleep, network loss, and reboot can invalidate connections; v1 offers manual restart. Forwarding startup checks do not verify the remote application, as documented for [ExitOnForwardFailure](https://man.openbsd.org/ssh_config#ExitOnForwardFailure).
172
+
173
+ ## 8. Storage and stack
174
+
175
+ | Concern | Implemented choice |
176
+ |---|---|
177
+ | Language and packaging | Python, project dependencies managed with uv; expose a `tunnel` console entry point. |
178
+ | Prompts | Questionary 2.1.1 for prompts, with tested filter and Esc bindings. |
179
+ | CLI and processes | Python standard library; keep argument parsing and process orchestration small. |
180
+ | SSH | Installed system OpenSSH, retaining native authentication and config behavior subject to the explicit safety boundaries above. |
181
+ | Persistence | Small local JSON records and per-tunnel diagnostic files; no separate database or daemon. |
182
+ | Installation | uv tool installation from Git or a local checkout; package `ssh-tunnel-cli`, Python 3.11 or later. |
183
+
184
+ [Questionary's documented prompt types](https://questionary.readthedocs.io/en/stable/pages/types.html) cover selections, text validation, and autocomplete; they do not prove the whole pictured interaction is available without customization. [uv's tool installation](https://docs.astral.sh/uv/guides/tools/) provides isolated environments and CLI executables. Pin compatible versions during implementation rather than treating the stable docs label as a dependency lock.
185
+
186
+ Proposed record fields: schema version, stable ID, alias, local bind/port, remote address/port, mode, created time, last start/stop observation, private control path, log path, and last error. Keep input settings separately identifiable from observed status. Never trust the saved status as current truth.
187
+
188
+ State lives in the private `~/.tunnel/` directory. Use owner-only directory/file permissions, atomic record replacement, and serialization of concurrent mutations. If startup succeeds but saving the record fails, stop the newly created connection so it cannot become an unmanageable orphan. Preserve and report corrupt state rather than overwriting it. Bound diagnostic retention and exclude authentication input; never enable verbose credential-sensitive capture by default.
189
+
190
+ ## 9. Required failure experiences
191
+
192
+ | Condition | Expected response |
193
+ |---|---|
194
+ | Missing config or no literal aliases | Explain how to add an alias manually, then offer reload/back. Do not create configuration. |
195
+ | Missing/unreadable include | Identify the file and show that the list may be incomplete. Do not silently claim complete discovery. |
196
+ | Invalid input | Explain the valid form beside the prompt and retain other answers. |
197
+ | Port occupied or bind fails | Offer another local port and return to review; no silent fallback or sudo. |
198
+ | Unknown host key/authentication request | Use native SSH prompts after Start; preserve verification and never store entered secrets. |
199
+ | Authentication, DNS, or network failure | Report the SSH failure without claiming a Tailscale diagnosis; offer retry/edit/back. |
200
+ | Remote app is down | Tunnel may still be Running. State that app health is not checked. |
201
+ | Cancellation during startup | Clean up any owned connection created by that attempt; no orphan or misleading Running entry. |
202
+ | Stale socket/state or reboot | Reconcile when inspected; keep settings available for a confirmed restart. |
203
+ | Unrelated SSH session on same host | Leave it unaffected by start/stop/restart of this utility's tunnel. |
204
+
205
+ ## 10. Acceptance criteria and verification plan
206
+
207
+ These are acceptance criteria. Automated coverage is in `tests/`. Read the implementation verification notes below for coverage limits.
208
+
209
+ 1. From `tunnel`, select an alias with the keyboard, enter ports, accept defaults, inspect the command, and start a usable local forward without typing SSH syntax.
210
+ 2. Test host fixtures containing multiple aliases, duplicates, comments, quoted paths, wildcard/negated patterns, nested/globbed includes, cycles, and unsupported conditional discovery. Selection cannot submit an arbitrary host.
211
+ 3. Back/edit preserve answers; cancelling at any pre-start stage creates no SSH connection. Invalid input and occupied ports cannot reach launch unchecked.
212
+ 4. Verify command preview corresponds exactly to the argument array used for execution, including quoting and mode. Inputs cannot introduce shell execution or extra SSH options.
213
+ 5. Against an isolated SSH test server and a known TCP/HTTP test app, verify foreground forwarding and Ctrl+C cleanup, then background forwarding after the initiating terminal closes.
214
+ 6. Keep an independent SSH session open to the same host while testing stop/restart; it must remain connected. Test conflicting inherited forwards/control settings and shared-socket configurations.
215
+ 7. Verify unknown-host and authentication prompts remain usable, failures produce truthful status, and no passwords or private key contents enter records/logs.
216
+ 8. Stop/restart, failed launch, interrupted startup, record-write failure, simultaneous manager invocations, and stale records cannot leave an untracked owned connection or destroy records.
217
+ 9. Stop the destination app while leaving SSH alive: the UI must distinguish tunnel state from app health. Simulate lost SSH connectivity and restart from saved settings after confirmation.
218
+ 10. Inspect the actual UI in Warp at a normal terminal size, with long aliases and without color. Check navigation, readable errors, wrapped commands, and the full foreground/background lifecycle.
219
+
220
+ Build in three small steps: prove native SSH lifecycle and ownership in an isolated harness; add the config picker and reviewed start flow; then add records, management, recent settings, and the complete acceptance pass. Do not build a daemon or a framework around this workflow.
221
+
222
+ ## 11. Design tradeoffs
223
+
224
+ Python + Questionary keeps the prompt flow and packaging small. Native OpenSSH handles authentication and connection configuration. A pair of explicit SSH commands isolates the selected forward from inherited forwarding settings. Recent settings, diagnostic viewing, and optional browser opening support repeated development use without adding a background manager.
225
+
226
+ The initial scope is macOS. Wider platform support, automatic reconnect, and a full-screen management interface are deferred. Engineering checks must continue to cover SSH lifecycle behavior, config discovery limits, and prompt bindings when dependencies change.
227
+
228
+ ## 12. References
229
+
230
+ - [OpenSSH client manual](https://man.openbsd.org/ssh)
231
+ - [OpenSSH configuration manual](https://man.openbsd.org/ssh_config)
232
+ - [Questionary prompt documentation](https://questionary.readthedocs.io/en/stable/pages/types.html)
233
+ - [uv tool installation](https://docs.astral.sh/uv/guides/tools/)
234
+
235
+ ## 13. Implementation decisions and verification
236
+
237
+ The implementation uses pinned Questionary 2.1.1 with uv, the Python standard library, and system OpenSSH. No external SSH library or manager daemon is added.
238
+
239
+ - State lives under private `~/.tunnel/` with atomic JSON replacement and a mutation lock. A short alternate `--state-dir` is supported for isolated use.
240
+ - Each reviewed start first opens a dedicated master with `ClearAllForwardings=yes`, then adds the selected route using `-O forward` with `ClearAllForwardings=no`. Commands for management use `-F /dev/null`; they do not re-evaluate connection config.
241
+ - Discovery resolves relative user Includes under `~/.ssh`. Conditional and dynamic Includes remain an explicitly warned limitation.
242
+ - Diagnostics are trimmed to 64 KiB when viewed, rather than rotated continuously by a daemon. Records remain until manually managed outside the app.
243
+ - Unit tests cover discovery, validation, command construction, private state, corruption, locking, and rollback. Opt-in integration tests use an isolated local sshd and generated test keys to check actual traffic, ownership, cancellation, stale/failed state, terminal closure, and native host-key confirmation.
244
+
245
+ The automated PTY tests are terminal interaction evidence, not manual verification in Warp. No existing SSH hosts, user keys, or Tailscale configuration are used by these tests. Password/passphrase authentication and platform support beyond macOS remain manual verification items.
@@ -0,0 +1,66 @@
1
+ Use case: ui-mockup.
2
+ Create one high-resolution, polished design review board for an interactive SSH tunneling CLI named "tunnel", using the attached Warp screenshot as the PRIMARY SCREENSHOT REFERENCE. This is a proposed terminal utility, not a Warp redesign.
3
+ Reference role: preserve the supplied screenshot's dark teal translucent terminal, city-building wallpaper, macOS traffic lights, compact top toolbar and centered Warp search box. Keep those recognizable in the main screen, but replace existing command/output with the proposed CLI. Increase terminal text contrast so every relevant label is readable. Do not copy user avatar or personal info. No website widgets, rounded web buttons, decorative glow, invented Warp navigation, or Figma editor chrome. All utility interaction is monospace terminal text, a single highlighted row, arrow cursor, simple rules, and keyboard hints.
4
+
5
+ Board composition: landscape, ideally 2400x1800 or larger. Professional dark charcoal surrounding canvas, generous margins, highly legible typography. Small title at top "tunnel / SSH without the command memorization" and explicit subtitle "Proposed UI — mockup". Short top note "Based on your Warp screenshot · Example SSH aliases".
6
+ Upper half: large faithful Warp window taking roughly 70% width, with a slim review annotation column on the right. Main Warp window shows host-selection step:
7
+ "$ tunnel"
8
+ "New tunnel · 1 of 5"
9
+ "Choose a machine"
10
+ "Search: mac"
11
+ selected teal row "❯ other-mac"
12
+ subline " SSH config alias"
13
+ muted row " lab-mac"
14
+ footer "↑ ↓ navigate Type to filter Enter select"
15
+ footer "Esc back Ctrl+C cancel"
16
+ Use wallpaper visible around the content, preserve main window screenshot chrome. Host examples clearly illustrative, do not suggest real SSH config has been inspected.
17
+ Right column has three restrained cyan numbered leader callouts outside the window: "1 Your SSH config supplies the host list"; "2 One question at a time"; "3 Review before anything connects". Thin cyan lines only, no overlap on text.
18
+
19
+ Below main screenshot: a tidy band of THREE equally sized enlarged terminal close-up panels, labeled outside the panels "02 / Connection details", "03 / Review & confirm", "04 / Manage tunnels". These are separate moments in the interaction, not simultaneous panes inside Warp. Use matching dark teal terminal style and crisp large monospaced text.
20
+
21
+ Connection details panel:
22
+ "✓ Machine other-mac"
23
+ "✓ Remote port 8790"
24
+ "✓ Local port 8790"
25
+ "✓ Destination 127.0.0.1"
26
+ blank line
27
+ "How should it run?"
28
+ "❯ Background"
29
+ " Foreground"
30
+ blank line
31
+ "Background: return to your shell"
32
+ "Foreground: Ctrl+C stops the tunnel"
33
+ External caption below panel: "Ports are separate prompts; destination defaults to the selected machine."
34
+
35
+ Review panel:
36
+ "Ready to start"
37
+ "Machine other-mac"
38
+ "Local 127.0.0.1:8790"
39
+ "Remote 127.0.0.1:8790"
40
+ "Mode Background"
41
+ blank line
42
+ "❯ Start tunnel"
43
+ " Show command"
44
+ " Edit settings"
45
+ " Cancel"
46
+ External caption: "Show the executable command on demand. Start requires Enter."
47
+
48
+ Management panel:
49
+ "$ tunnel"
50
+ "Manage tunnels"
51
+ "❯ other-mac :8790 Running"
52
+ " dev-server :3000 Disconnected"
53
+ blank line
54
+ "Selected: other-mac :8790"
55
+ " Details / Show command"
56
+ " View logs"
57
+ " Stop tunnel"
58
+ " Back"
59
+ External caption: "Select a tunnel, then an action. Disconnected entries offer Restart."
60
+
61
+ Bottom compact state strip, exactly three small meaningful terminal excerpts, labeled outside as "Connecting", "Running", "Port occupied":
62
+ Connecting: "Connecting to other-mac…" and small note "SSH may request authentication"
63
+ Running: "✓ Tunnel running in background" then "http://127.0.0.1:8790" and small note "Tunnel active; app health not checked"
64
+ Port occupied: "Port 8790 is already in use" then "Choose another local port: 8791"
65
+ These are mock examples, no claim actual tunnel has been started.
66
+ Bottom tiny footer "Design proposal only · No tunnel started". Maintain aligned margins, complete readable labels, no overflowing or clipped text. The visual should feel like a plausible Questionary-style CLI inside the user's real terminal appearance, not an elaborate full-screen dashboard.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ssh-tunnel-cli"
7
+ version = "0.1.1"
8
+ description = "Guided local SSH tunnels using your existing OpenSSH configuration"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Operating System :: MacOS :: MacOS X",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Internet",
19
+ ]
20
+ dependencies = ["questionary==2.1.1", "prompt-toolkit==3.0.53"]
21
+
22
+ [project.urls]
23
+ Repository = "https://github.com/maneeshv/ssh-tunnel-cli"
24
+ Issues = "https://github.com/maneeshv/ssh-tunnel-cli/issues"
25
+
26
+ [project.scripts]
27
+ tunnel = "tunnel_cli.cli:main"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/tunnel_cli"]
31
+
32
+ [tool.hatch.build.targets.sdist]
33
+ only-include = ["src", "tests", "design", "README.md", "SPEC.md", "LICENSE", "pyproject.toml", "uv.lock"]
@@ -0,0 +1 @@
1
+ """Interactive local forwards managed by system OpenSSH."""