Initial commit: dial — interactive SSH host picker
A fast, read-only ~/.ssh/config picker that exec's the system ssh client. Includes: static high-contrast picker with recency/frequency/name sort and tag grouping, `dial keygen` (native ed25519/RSA + optional config entry), and an optional offline YubiKey launch gate with one-time recovery codes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
// Package connect turns a selected host alias into a handover to the system's
|
||||
// real ssh client. It is deliberately decoupled from the picker: the picker's
|
||||
// job ends at "here is an alias", and connect's job is "hand the terminal to
|
||||
// ssh <alias>". This keeps the select-a-host logic decoupled from the handover.
|
||||
package connect
|
||||
|
||||
// Command describes an external client invocation that dial hands the terminal
|
||||
// over to. dial never wraps or proxies the session; it exec's the real binary.
|
||||
type Command struct {
|
||||
Bin string // program to run, resolved via PATH (e.g. "ssh", "sftp")
|
||||
Args []string // arguments after the program name (argv0 is added by Exec)
|
||||
}
|
||||
|
||||
// SSH opens an interactive shell session on alias, letting the system ssh
|
||||
// client re-resolve everything (ProxyJump, agent forwarding, tty, etc.).
|
||||
func SSH(alias string) Command {
|
||||
return Command{Bin: "ssh", Args: []string{alias}}
|
||||
}
|
||||
|
||||
// Exec hands terminal control to the command. On Unix this replaces the current
|
||||
// process image, so it only returns if the exec itself fails (e.g. the binary
|
||||
// is missing). On Windows it runs the child with inherited stdio and exits with
|
||||
// the child's status, so it likewise does not return on success.
|
||||
func (c Command) Exec() error { return execCommand(c) }
|
||||
@@ -0,0 +1,21 @@
|
||||
//go:build !windows
|
||||
|
||||
package connect
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// execCommand replaces the current process with the target binary via execve.
|
||||
// This is what makes ssh's tty/ProxyJump/agent-forwarding "just work": dial is
|
||||
// gone, and ssh owns the terminal directly with no wrapping parent.
|
||||
func execCommand(c Command) error {
|
||||
path, err := exec.LookPath(c.Bin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
argv := append([]string{c.Bin}, c.Args...)
|
||||
return syscall.Exec(path, argv, os.Environ())
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//go:build windows
|
||||
|
||||
package connect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// execCommand runs the target as a child with inherited stdio because Windows
|
||||
// has no execve-style process replacement. dial waits and then exits with the
|
||||
// child's status, so from the user's perspective control still passes cleanly
|
||||
// to ssh.exe (found on PATH; typically %WINDIR%\System32\OpenSSH).
|
||||
func execCommand(c Command) error {
|
||||
path, err := exec.LookPath(c.Bin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(path, c.Args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
var ee *exec.ExitError
|
||||
if errors.As(err, &ee) {
|
||||
os.Exit(ee.ExitCode())
|
||||
}
|
||||
return err
|
||||
}
|
||||
os.Exit(0)
|
||||
return nil // unreachable
|
||||
}
|
||||
Reference in New Issue
Block a user