# dial — v2 report

A status report on `dial` after the v2 pass, written to be self-contained so it
can be pasted into a fresh chat for brainstorming. No codebase access needed to
follow it. (For the original tool, see the v1 REPORT.md; this focuses on what v2
added and where it stands.)

## What dial is (one paragraph)

`dial` is a fast, cross-platform command-line SSH tool. Its core is an
interactive host picker: it reads `~/.ssh/config`, shows a clean, static,
high-contrast, low-light-friendly list, and on selection `exec`s the real system
`ssh` client into the host (so tty/ProxyJump/agent forwarding just work). It's a
single Go binary. v2 added sorting/grouping, an SSH key generator, and an in-app
SFTP file browser. Optional features include a YubiKey launch gate. App data
(recents, favourites, settings) lives under `~/.ssh/.dial/` so it syncs via the
user's existing Syncthing.

## What v2 shipped

1. **Alphabetical sort.** The `s` key now cycles three modes: recently used →
   most used → name. Favourites still pin to the top in every mode. Persisted.

2. **Last-connected timestamp** shown in the host preview pane ("1 hour ago" /
   "never"), surfacing the recency data already tracked for sorting.

3. **Tag grouping + coloured tag pills.** A `g` toggle (independent of sort)
   groups the list into sections: a Favourites section, one section per tag
   (alphabetical), then Untagged. Tag pills are coloured deterministically per
   tag from the theme's high-contrast palette. **Decision made:** grouping is an
   independent toggle (not a 4th sort mode), and a multi-tagged host appears
   under its *first* tag only, so the list stays a clean permutation with no
   duplicated rows.

4. **`dial keygen`.** A new subcommand that generates SSH keys **natively in Go**
   (no `ssh-keygen` dependency): ed25519 by default, `--rsa` for RSA-4096.
   Optional passphrase (default none). It keeps the `.pub` file (the old shell
   script deleted it), writes the private key `0600`, and can append a matching
   `Host` block to `~/.ssh/config` — backing the file up first and refusing to
   duplicate an existing alias. It's a rebuild of an old `create_ssh_keypair.sh`,
   fixing that script's macOS-only `sed`, key-deletion, and unquoted-append bugs.

5. **In-app SFTP file browser.** Pressing `t` on a host opens a dual-pane
   browser (local left, remote right) that transfers files without leaving dial:
   - Side-by-side panes, `tab` to switch, arrow/`jk` to move, `enter` to descend,
     `backspace` up, `space` to batch-select.
   - Directional transfers: `→`/`p` push local→remote, `←`/`g` pull remote→local,
     into the other pane's current directory.
   - Recursive directory transfers, aggregate progress bar, and overwrite prompts
     (`[o]verwrite`/`[s]kip`/`[r]ename`, uppercase = apply to all remaining).
   - **Same-session resume**: an interrupted transfer retried in the same session
     resumes partial files instead of restarting.
   - Connects over SFTP in-process (`pkg/sftp` + `golang.org/x/crypto/ssh`),
     reusing the host's real `~/.ssh/config` (resolved via `ssh -G`), including
     ProxyJump, authenticating via ssh-agent or unencrypted key files.
   - When the YubiKey gate is enabled it re-challenges before opening the browser.

## Architecture (packages)

Existing v1 packages: `sshconf` (read-only config parser), `store`
(`~/.ssh/.dial/` persistence), `picker` (the TUI), `connect` (exec-into-ssh),
`yubikey` (gate + recovery code), `theme` (styling). v2 added:

- `internal/keygen` — native key generation (crypto + `x/crypto/ssh`), key-file
  writing with correct perms, and safe `~/.ssh/config` appends.
- `internal/xfer` — the transfer engine. A small `fs` abstraction lets push and
  pull share one recursive copy routine; handles progress, conflict policy, and
  resume. **Fully unit-tested against an in-memory SFTP server** (no sshd needed).
- `internal/sshx` — the connection layer. Resolves effective config via
  `ssh -G <alias>`, builds the `ssh.ClientConfig` (agent + key auth, known_hosts
  verification), and dials directly or through a ProxyJump chain.
- `internal/browser` — the dual-pane Bubble Tea file browser, using `xfer` for
  transfers and coordinating the overwrite prompt across goroutines.

The picker's `t` key returns an `ActionSFTP` result; `main` then (re-)runs the
gate, connects via `sshx`, opens an `sftp.Client`, and runs the browser. The
default flow is now a loop: pick → connect (exec, ends) or sftp (returns to the
picker). Host *selection* stayed decoupled from the *action*, which is what let
SFTP reuse the picker cleanly.

## Current state

- **Complete and green.** All packages build; `go vet` clean; unit tests pass
  for `sshconf`, `store`, `yubikey`, `picker`, `keygen`, `xfer`, `sshx`.
- **Cross-compiles** to all five targets (macOS arm64/amd64, Linux amd64/arm64,
  Windows amd64). Binary grew from ~3.5 MB to ~6.1 MB — the cost of the
  in-process SFTP stack (`x/crypto/ssh` + `pkg/sftp`), a deliberate trade chosen
  over shelling out to the system `sftp`.
- **Live-verified via tmux** (against an in-memory SFTP server): dual-pane
  render, navigation, batch selection, single-file push, recursive directory
  push (contents verified on the far side), and the overwrite-conflict prompt
  (goroutine↔UI coordination). keygen verified end-to-end (valid ed25519,
  correct perms, config append with backup).

## Known limitations / not yet exercised

- **The SFTP connection layer is now live-verified against a real sshd.**
  Against a test host (key-based auth, no agent, unencrypted key loaded directly
  from `IdentityFile`): `sshx.Connect` + SFTP listing, an SFTP write/read/remove
  round-trip, and a full-TUI pull *and* push both passed with byte-for-byte
  sha256 matches. An env-gated live test (`DIAL_LIVE_HOST=<host> go test
  ./internal/sshx -run TestLiveConnect`) is kept for re-running this. Still
  unverified against a real host: **ProxyJump chaining** and **agent-backed /
  encrypted-key auth** (the test host used a plain key file, no agent). (Same
  hardware-untested posture remains for the YubiKey gate.)
- **ssh-agent on Windows** uses named pipes, not the `SSH_AUTH_SOCK` unix socket
  the code dials; it compiles, but agent auth on Windows likely needs follow-up.
- **Encrypted key files** are skipped by the SFTP auth (it relies on the agent
  for those) — dial never prompts for a key passphrase when connecting.
- **No mid-transfer cancel** — resume covers interruptions, but there's no
  in-flight cancel key; a stuck transfer would need Ctrl-C out of the browser.
- **known_hosts is verify-only** — an unknown host errors out ("ssh to it once
  first") rather than offering trust-on-first-use; no known_hosts management.
- Still unsigned binaries (Gatekeeper/SmartScreen warnings); no CI/releases.

## Deferred (explicitly out of this pass)

Idle-timeout gate option; crash-persistent resume (surviving a dial restart);
distribution/signing/package managers; multi-select/batch actions *across hosts*
(distinct from the in-browser batch file selection, which shipped); per-host
notes; fuzzy search.

## Open questions & directions to brainstorm

1. **Real-host validation plan for SFTP** — what's the cheapest way to smoke-test
   the connection layer (a throwaway VM? a container running sshd? a known host)?
2. **Windows agent support** — is Windows a real target for the SFTP browser, or
   is it macOS/Linux-first? Determines whether named-pipe agent support is worth
   building.
3. **Cancel & crash-persistent resume** — how often do transfers of very large
   files over flaky links actually happen? That's the trigger for investing in
   disk-persisted transfer state.
4. **known_hosts UX** — is verify-only acceptable, or should the browser offer to
   record a new host key (with a clear prompt)?
5. **Grouping refinements** — should multi-tagged hosts optionally appear under
   *every* tag? Should there be a per-tag filter shortcut or colour legend?
6. **Distribution** — Homebrew tap / Scoop / `install.sh` / signed releases via
   CI: worth doing now that the feature set is broad?
7. **SFTP polish** — a file preview? delete/rename/mkdir on either side? a "sync
   this directory" one-shot? These edge toward a full file manager — where's the
   line for a tool that's fundamentally a host picker?

## How to build / try

- Build: `make build` (native) or `make dist` (all platforms). Requires Go 1.23+.
- Keys: `dial keygen` (no external deps).
- SFTP browser: `dial`, pick a host, press `t` (needs the host in known_hosts and
  agent/key auth working — i.e. a host you can already `ssh` to).
- YubiKey gate: `dial yubikey enable` (requires Yubico's `ykman`).
