Imported from jlcai09/chemssh-launcher (
AGENTS.md). Install upstream withnpx skills add jlcai09/chemssh-launcher. Copyright stays with the author.
AGENTS.md
Project Purpose
This project is a standalone ChemSSH launcher written in Go. It should produce a small single-file executable that can connect to a remote server over SSH, run ChemSSH there, create a local SSH tunnel, check the local URL, and open the browser.
The launcher exists because ChemSSH is usually run on a remote Linux server or HPC login node while the browser runs locally. Users should not need to manually open two SSH sessions.
Core Direction
- Language: Go.
- SSH implementation:
golang.org/x/crypto/ssh. - Runtime dependencies: no Python, no Node, no system
sshrequirement. - Output: one executable per platform via
go build. - First UI: CLI or simple terminal UI. Keep code structured so GUI can be added later.
Encoding
- Decode and write repository text files as UTF-8.
- Documentation files, especially README.md, README.zh-CN.md, and AGENTS.md, must remain valid UTF-8.
ChemSSH Context
ChemSSH normally starts like this on the remote machine:
chemssh --config config.yaml --host 127.0.0.1 --port 8888
It is intentionally bound to 127.0.0.1 on the remote server. The launcher should forward a local port to that remote loopback address:
127.0.0.1:8888 -> 127.0.0.1:8888
If 127.0.0.1:8888 is occupied locally, ask the user to change the local port. Prefer changing the port over changing 127.0.0.1 to another loopback address.
Do not default to 0.0.0.0.
Required Features
Implement these before polishing:
- Multiple server profiles.
- Encrypted credential storage.
- Password login.
- Private key login.
- Multi-line pre-start commands.
- Multi-line ChemSSH start command.
- Local port conflict detection.
- SSH local port forwarding.
- Health check for local URL.
- Optional browser auto-open.
- Stop session and close tunnel.
Profile Model
Profiles should have stable IDs. Names are user-facing and can change.
Suggested fields:
type Profile struct {
ID string `json:"id"`
Name string `json:"name"`
SSHHost string `json:"ssh_host"`
SSHPort int `json:"ssh_port"`
SSHUser string `json:"ssh_user"`
AuthMethod string `json:"auth_method"`
HasPassword bool `json:"has_password"`
PrivateKeyPath string `json:"private_key_path"`
HasPrivateKeyPassphrase bool `json:"has_private_key_passphrase"`
RemoteHost string `json:"remote_host"`
RemotePort int `json:"remote_port"`
LocalHost string `json:"local_host"`
LocalPort int `json:"local_port"`
LocalURLPath string `json:"local_url_path"`
PreStartCommands string `json:"pre_start_commands"`
StartCommand string `json:"start_command"`
HealthCheckURL string `json:"health_check_url"`
OpenBrowser bool `json:"open_browser"`
HasSecurityToken bool `json:"has_security_token"`
}
Do not store raw passwords or passphrases in this struct.
Secret Storage Rules
Secrets must be encrypted at rest.
Preferred implementation:
- Use an OS credential store library such as
github.com/zalando/go-keyring. - Store profile JSON separately from secrets.
- Use keys like:
- service:
chemssh-launcher - username:
<profile-id>:password - username:
<profile-id>:key-passphrase - username:
<profile-id>:security-token
- service:
Fallback if keyring is unavailable:
- Implement an encrypted vault file.
- Use Argon2id for key derivation.
- Use AES-GCM or XChaCha20-Poly1305.
- Never store the vault password.
Editing rule:
- A stored password must never be shown again.
- Edit screens/prompts can show
savedornot set. - Provide actions to replace or clear the secret.
Logging rule:
- Never log decrypted secrets.
- Never include secrets in errors.
- Never pass passwords through process command-line arguments.
Remote Command Semantics
Users can enter multi-line setup commands, including cd, source, conda activate, module load, or env exports.
Run pre-start commands and start command in the same remote shell so environment changes carry forward.
Conceptual command:
set -e
<pre_start_commands>
<start_command>
Keep stdout/stderr visible to the user. These logs are important when remote environment setup fails.
If the configured remote port is 8888 but the custom start command appears to use another port, warn instead of rewriting user commands.
SSH Tunnel Behavior
Implement local forwarding with a local TCP listener:
- Listen on
profile.LocalHost:profile.LocalPort. - For each local connection, call
sshClient.Dial("tcp", remoteHostPort). - Copy bytes in both directions until closed.
Remote target:
profile.RemoteHost:profile.RemotePort
Default:
127.0.0.1:8888
Local bind default:
127.0.0.1:8888
Warn if LocalHost is 0.0.0.0 or a non-loopback address.
Port and Health Checks
Before SSH connection:
- Test whether the local bind address and port can be listened on.
- If not available, show the conflict and suggest nearby free ports.
After starting remote command and tunnel:
- Poll
http://<local_host>:<local_port><local_url_path>, unlessHealthCheckURLis set. - Treat HTTP status 200 through 499 as service reachable.
- Retry until timeout.
- On success, optionally open browser.
Suggested Packages
Keep dependencies modest.
Likely dependencies:
golang.org/x/crypto/sshgolang.org/x/termfor password inputgithub.com/zalando/go-keyringfor OS credential storage
Optional:
github.com/spf13/cobrafor CLI commandsgithub.com/google/uuidfor profile IDs
Avoid Electron-style stacks. This is intended to stay small.
CLI Target
Initial commands:
chemssh-launcher profile list
chemssh-launcher profile add
chemssh-launcher profile edit <name-or-id>
chemssh-launcher profile delete <name-or-id>
chemssh-launcher profile test <name-or-id>
chemssh-launcher start <name-or-id>
Interactive profile creation is acceptable and preferred for v1.
Prompt defaults:
- SSH port:
22 - Remote host:
127.0.0.1 - Remote port:
8888 - Local host:
127.0.0.1 - Local port:
8888 - Local URL path:
/ - Open browser:
true
Build Configuration
Default release builds (three recommended variants):
# Browser mode (opens in system browser)
go run ./tools/build --windowsgui # Standard browser mode, no console
# WebView2 mode (embedded browser window)
go run ./tools/build --webview2 --windowsgui # WebView2 mode, no console (best UX)
go run ./tools/build --webview2 # WebView2 mode with console (for debugging)
The build tool enables size and startup speed optimizations by default. Use --no-optimize to disable for debugging.
Version comparison:
- Browser mode: Uses system browser, lighter weight, easier to debug network requests
- WebView2 mode: Embedded window, better integration, smoother user experience
--windowsgui: Hides console window, cleaner for end users, harder to debug startup issues
Code Organization
Start with this structure:
cmd/chemssh-launcher/main.go
internal/app/
internal/config/
internal/secret/
internal/sshclient/
internal/netcheck/
internal/browser/
internal/ui/
Keep package responsibilities clear:
config: profile structs, config paths, JSON load/save.secret: keyring and fallback vault.sshclient: auth, remote command, tunnel.netcheck: local port checks and URL health checks.browser: platform browser opening.app: orchestration and session lifecycle.ui: CLI prompts and command wiring.
Testing Expectations
Add focused tests for:
- Profile JSON round trip.
- Secret store interface with fake implementation.
- Password edit behavior: saved secrets are not returned to UI for display.
- Local port availability detection.
- Suggested alternate ports.
- Health check success/failure behavior.
- Command assembly preserves multi-line pre-start commands.
Tunnel integration tests can be added later with a local SSH test server or manual test script.
Implementation Priorities
- Create profile storage and CLI skeleton.
- Add secret storage abstraction.
- Add password and private key auth.
- Add port conflict check.
- Add SSH connection and remote command execution.
- Add tunnel.
- Add health check and browser open.
- Improve lifecycle shutdown.
- Add tests around each completed layer.
Safety Constraints
- Do not store passwords in plain JSON.
- Do not print passwords.
- Do not show saved passwords while editing.
- Do not silently bind to
0.0.0.0. - Do not rewrite custom user commands behind their back.
- Do not require ChemSSH to listen on a public interface.
- Do not assume the remote machine has npm, conda, module, or bash unless the user configured commands accordingly.
Manual Smoke Test
Expected happy path:
- Build the launcher.
- Add a profile with password auth.
- Use pre-start commands:
cd /home/user/chemssh
source .venv/bin/activate
- Use start command:
chemssh --config config.yaml --host 127.0.0.1 --port 8888
- Run:
chemssh-launcher start <profile>
- Confirm local URL opens:
http://127.0.0.1:8888/
- Stop launcher and confirm the tunnel closes.