# Project Brief: Interactive SSH Host Picker CLI ("dial") ## Overview A fast, interactive command-line tool that reads `~/.ssh/config`, lets the user fuzzy-select a host, and `exec`s the system `ssh` binary into it. Cross-platform (macOS Apple Silicon/M4, Linux, Windows). Optional YubiKey gate before the app will run. App-specific data lives inside the `.ssh` tree so it rides along on existing Syncthing sync across machines. ## Goals - Parse `~/.ssh/config`, including `Include` directives. - Present an interactive, fuzzy-searchable list of `Host` entries. - On selection, `exec` the real `ssh` binary (not a wrapped subprocess) so the terminal/tty, `ProxyJump`, agent forwarding, etc. all just work. - Cold start fast enough to feel instant (target: comfortably under 100ms). - Ship as a single static binary per platform — no runtime dependency. - Optional YubiKey challenge-response gate required before the picker unlocks. - Store app data (recents, favorites, tags, settings) under `~/.ssh/` so it syncs automatically via the existing Syncthing setup — no new sync config needed. ## Core Features 1. **Config parsing** - Read `~/.ssh/config`, follow `Include` lines. - Surface `Hostname`, `User`, `Port`, `ProxyJump` as preview info. - Optional lightweight tagging via comment convention, e.g. `# tag: prod, riedel` above a `Host` block — used for filtering/grouping. - Never write to `~/.ssh/config` — strictly read-only. 2. **Interactive picker** - **Static, numbered list** — not a live fuzzy-filter-as-you-type interface. Explicitly avoid the cramped/fuzzy-filter style seen in tools like the `rclone` installer, which is hard to read in low light. Prioritize legibility: larger clear text, generous spacing between entries, high contrast, no small dense text packed together. - Navigate with arrow keys or number keys, enter to connect, esc to quit. - Optional `/` to jump into a search/filter mode on demand (not the default view) if the host list gets long — but the resting state should always be a clean, static, readable list. - Sort by most-recently-used or most-frequently-used, toggleable. - Show a small preview area (Hostname/User/Port/Jump host) for the highlighted entry, in equally readable, high-contrast text. 3. **Connect** - `exec` (or platform equivalent) into `ssh ` so control of the terminal passes cleanly to the ssh session — this app should not remain a parent process wrapping it. 4. **YubiKey gate (optional, toggle in settings)** - HMAC-SHA1 challenge-response (YubiKey slot 2), fully offline — no internet or server dependency. - Implemented by shelling out to `ykman` (Yubico's official CLI) rather than a direct PC/SC library. Trade-off accepted: `ykman` (and its Python dependency) must be installed separately on every machine this runs on — this breaks the "single static binary, zero deps" goal for the YubiKey feature specifically, but keeps the implementation simple and leans on Yubico's own well-tested tool. `dial` should check for `ykman` on `PATH` at startup and give a clear error if it's missing. - Require physical touch to confirm. - **Backup 2FA code**: generate a one-time recovery code when the gate is first enabled, shown once and expected to be stored by the user somewhere separate (password manager, printed, etc.). This code lets the user in once if the YubiKey isn't available, avoiding total lockout. After use it should be invalidated/regenerated, not reusable indefinitely. 5. **Sync-safe app data** - New directory: `~/.ssh/.dial/` - `settings.json` — yubikey on/off + slot, theme, sort preference, etc. - `data.json` — recents, favorites, tag cache. - Multiple machines may write this concurrently via Syncthing — design for that: last-write-wins is acceptable for v1, but reads should tolerate a mid-sync/partial file without crashing (e.g. read failure just falls back to defaults rather than erroring out). - Nothing secret is ever written here — the YubiKey secret never leaves the key itself in challenge-response mode, so there's no key material to accidentally sync. ## Non-Functional Requirements - No telemetry, no network calls (beyond ssh itself). - Read-only with respect to `~/.ssh/config`. - Graceful degradation if no YubiKey is plugged in and the gate is enabled. - Works identically on macOS (Apple Silicon), Linux, and Windows — including Windows' different `ssh.exe` path/behavior. - **Readable in low light**: default view is a static list with clear, well-spaced, high-contrast text — not a dense fuzzy-search UI. This is a hard requirement, not a nice-to-have. ## Recommended Tech Stack **Go** — chosen over your usual Python/Bash default specifically because this needs true single-binary cross-platform distribution and fast cold start; Python's interpreter startup and packaging story (PyInstaller etc.) are a worse fit here. - TUI: `charmbracelet/bubbletea` + `lipgloss` (or simpler: `charmbracelet/huh` for a lighter list-picker if a full TUI is overkill). - SSH config parsing: `kevinburke/ssh_config`. - YubiKey challenge-response: shell out to `ykman` (simplest, but adds an external dependency) or use a Go PC/SC library directly for a fully self-contained binary — worth deciding based on how important "zero external deps" is to you. - Cross-compile via `GOOS`/`GOARCH` for the three targets from one machine. *Alternative:* Rust would give similar performance/single-binary benefits with a steeper setup cost — mention if you'd rather Claude Code default to that instead. ## Out of Scope (v1) - Editing/managing `~/.ssh/config` from within the tool. - Remote host key (known_hosts) management. - Any custom ssh-agent handling — rely on the OS-native ssh client already installed. ## Future / v2 Considerations Not in scope for v1, but the architecture should not preclude these — worth keeping the "select a host" logic reusable/decoupled from the "connect via ssh" action so these can be added later without a rewrite: - **SFTP file transfer**: after selecting a host, an alternate action to send/receive files instead of opening a shell session. - Simplest version: shell out to the system `sftp`/`scp` binary, same pattern as the `ssh` exec flow. - Fuller version: an in-app file browser (source/dest picker, remote directory listing, progress) built against a Go SFTP library (e.g. `pkg/sftp`) rather than shelling out — should follow the same readable, high-contrast, low-light-friendly list UI as the host picker, not the cramped style of a bare `sftp` prompt. ## Open Decisions (Claude Code should confirm before building) - YubiKey gate: required every launch, or only after an idle timeout? - Exact format/length of the backup recovery code, and where it should be displayed/stored on generation (e.g. printed to terminal only, once).