# 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 <alias>` 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).
