Files
dial/docs/v2-report.md
T
bsncubed 8af44dfcd2 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>
2026-07-22 23:18:23 +10:00

8.6 KiB

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 execs 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).