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:
+211
@@ -0,0 +1,211 @@
|
||||
# dial — v2 Plan
|
||||
|
||||
Written against the v1 REPORT.md handoff. v1 is complete, tested, and out of
|
||||
scope to modify except where noted. This plan is scoped to be handed to
|
||||
Claude Code as-is; every judgment call is flagged so it can be overridden.
|
||||
|
||||
---
|
||||
|
||||
## 1. Alphabetical sort (small, do first)
|
||||
|
||||
- Add a third sort mode to the existing `s` toggle: **recently used → most
|
||||
used → alphabetical → (back to recently used)**.
|
||||
- Alphabetical sorts by `Host` alias (the thing the user actually types/sees
|
||||
first in the list), case-insensitive.
|
||||
- Favourites still pin to the top regardless of sort mode (unchanged v1
|
||||
behaviour).
|
||||
- Persist the selected mode the same way recents/frequency already persist
|
||||
(`~/.ssh/.dial/data.json`).
|
||||
|
||||
No open questions here — this is a straightforward extension of existing code.
|
||||
|
||||
---
|
||||
|
||||
## 2. SSH key generation tool (`dial keygen`)
|
||||
|
||||
Rebuilt from the logic in `create_ssh_keypair.sh`, fixing the cross-platform
|
||||
and UX issues found in it, and wired into `dial` as a new subcommand rather
|
||||
than folded into the interactive picker.
|
||||
|
||||
**Why a separate subcommand, not part of the picker:** the picker's core
|
||||
read-only-config guarantee is a deliberate v1 property. Keygen necessarily
|
||||
*writes* to `~/.ssh/config` and creates key files, so it should be a distinct,
|
||||
explicitly-invoked action (`dial keygen`) — not something that can happen by
|
||||
accidentally pressing a key while browsing hosts.
|
||||
|
||||
### Fixes over the original script
|
||||
- Replace the macOS-only `sed -i ''` public-key-comment logic with something
|
||||
that works identically on macOS/Linux/Windows (Go string handling, no shell
|
||||
`sed` dependency at all).
|
||||
- Default algorithm: **ed25519**. Offer `--rsa` (4096-bit) as an explicit
|
||||
opt-out for hosts that don't support ed25519 (some older network gear, e.g.
|
||||
legacy broadcast equipment, may not).
|
||||
- **Keep the `.pub` file.** Don't delete it after displaying it once — that
|
||||
behaviour in the original script means a lost copy-paste = a wasted key.
|
||||
Still print it to the terminal for convenience, but leave the file in place.
|
||||
- Proper quoting/escaping when appending to `~/.ssh/config` (avoid the blind
|
||||
`read` + string-concat approach in the original).
|
||||
|
||||
### Proposed flow
|
||||
```
|
||||
dial keygen
|
||||
→ prompt: key name / label
|
||||
→ prompt: algorithm (default ed25519, flag for rsa)
|
||||
→ generate into ~/.ssh/<label> (and .pub alongside, kept)
|
||||
→ prompt: add a Host entry to ~/.ssh/config now? (y/n)
|
||||
→ if yes: Host alias / HostName / User / Port (default 22) / tag (optional, using existing `# tag:` convention)
|
||||
→ print the public key once for easy copy, key file left on disk
|
||||
```
|
||||
|
||||
**Assumption flagged:** this reuses `dial`'s existing static-list, low-light-
|
||||
friendly prompt styling rather than plain unstyled `read` prompts like the
|
||||
original script, for consistency with the rest of the tool.
|
||||
|
||||
---
|
||||
|
||||
## 3. Remaining v2 features (from REPORT.md open questions)
|
||||
|
||||
Going through the report's nine open questions individually, with a decision
|
||||
made and flagged for each so this can go straight to Claude Code.
|
||||
|
||||
### 3.1 SFTP / file transfer mode — **in scope for v2: full in-app browser**
|
||||
- After selecting a host, a second action (`t` for transfer, alongside `enter`
|
||||
to shell in) opens an **in-app SFTP file browser** — not a shell-out to the
|
||||
system `sftp` binary.
|
||||
- Built directly against a Go SFTP library (`pkg/sftp` + `golang.org/x/crypto/
|
||||
ssh` for the underlying connection) rather than shelling out, so dial owns
|
||||
the whole UI.
|
||||
- **Core browser UX** (following the same static, high-contrast, low-light-
|
||||
friendly list styling as the host picker — no cramped/dense text):
|
||||
- Split view or toggle-view of local vs. remote directory listing.
|
||||
- Arrow-key navigation through directories on both sides; `enter` on a
|
||||
directory descends into it, a clear "up one level" entry at the top of
|
||||
each listing.
|
||||
- **Batch selection:** a keypress (e.g. `space`) toggles selection on the
|
||||
highlighted entry without moving the cursor, same pattern as v1's `f`
|
||||
favourite toggle. Multiple files (and/or directories) can be selected
|
||||
before triggering a push/pull.
|
||||
- Push (local → remote) or pull (remote → local) on the current selection —
|
||||
a single file, multiple files, a single directory, or a mix.
|
||||
- **Directory transfers are recursive**, walking the full tree on the
|
||||
source side and recreating structure on the destination side.
|
||||
- Progress UI: per-item progress plus an aggregate "3 of 12 files, 40% of
|
||||
total" view when a batch/directory transfer is in flight — legible at the
|
||||
same size/contrast as the rest of the UI.
|
||||
- Clear error states: connection drop mid-transfer, permission denied, disk
|
||||
full, file already exists (prompt overwrite/skip/rename rather than
|
||||
silently clobbering) — applies per-item within a batch, with an option to
|
||||
apply the same choice to all remaining conflicts rather than prompting
|
||||
per file.
|
||||
- **Resume on failure: basic, same-session only.** If a transfer (or a batch/
|
||||
directory transfer) is interrupted — network drop, connection reset — and
|
||||
retried within the same `dial` session, dial seeks both the local and
|
||||
remote file to the last confirmed byte offset and continues rather than
|
||||
restarting from zero. This applies per-file within a batch/directory
|
||||
transfer (finished files stay finished; only the interrupted one resumes).
|
||||
- **Explicitly not included:** resume that survives closing/restarting
|
||||
`dial` or the machine. That requires persisting transfer state to disk and
|
||||
verifying partial files weren't corrupted before trusting them — a
|
||||
meaningfully bigger, separate piece of work. Flag if this turns out to
|
||||
matter in practice (e.g. regularly moving very large files where a lost
|
||||
session is costly) and it can be scoped as a v2.x follow-up.
|
||||
- Reuses the connection details already resolved from `~/.ssh/config` for the
|
||||
selected host (same auth, same `ProxyJump` if present) — no separate
|
||||
credential entry.
|
||||
- **Gate coverage:** as already decided in §3.4, the YubiKey gate (when
|
||||
enabled) covers entry into this mode too, not just app launch.
|
||||
|
||||
### 3.2 How to pick the action (shell vs SFTP) — **decided**
|
||||
- Second keypress on the highlighted host, not a separate menu: `enter` = ssh,
|
||||
`t` = sftp. Keeps the static-list simplicity intact rather than adding a
|
||||
submenu/modal.
|
||||
|
||||
### 3.3 Distribution/trust (signing, Homebrew, Scoop, CI) — **deferred, not v2**
|
||||
- This is packaging/ops work, not a feature, and doesn't block daily use on
|
||||
your own machines via Syncthing + manual install. Flagging as a separate
|
||||
future task rather than bundling into this feature-focused v2 pass.
|
||||
|
||||
### 3.4 Gate ergonomics — **small change: gate now also covers SFTP**
|
||||
- Every-launch touch stays as-is (no idle-timeout added — the report notes
|
||||
this as a "worth adding later" maybe, not a v2 must-have).
|
||||
**Assumption flagged:** leaving idle-timeout out of v2; say the word if you
|
||||
want it added now instead.
|
||||
- The YubiKey gate (when enabled) now also gates the new `t` (SFTP) action,
|
||||
not just app launch, since it's a second way to move data off/onto a host.
|
||||
|
||||
### 3.5 Recovery-code UX — **unchanged**
|
||||
- Current design (terminal-only, shown once, single-use, auto-rotates) stays
|
||||
as-is. No changes requested and the report doesn't flag it as a problem.
|
||||
|
||||
### 3.6 Richer metadata (tag grouping/colour-coding) — **in scope, small**
|
||||
- Add optional **section headers by tag** in the static list when sorted
|
||||
alphabetically or by tag (new sort mode candidate — see note below).
|
||||
- Colour-code tag pills using the existing `internal/theme` high-contrast
|
||||
palette (e.g. a distinct colour per tag), not free-form colour — keeps the
|
||||
low-light-readable requirement intact.
|
||||
- **Open question for you, not decided here:** should "group by tag" be its
|
||||
own 4th+ sort mode, or a toggle independent of sort? Recommend: independent
|
||||
toggle (`g` to group), since it's orthogonal to alphabetical/recent/most-used
|
||||
ordering. Flagging for Claude Code to confirm with you if ambiguous during
|
||||
build.
|
||||
|
||||
### 3.7 Search behaviour (fuzzy vs substring) — **kept as substring, not changed**
|
||||
- Report explicitly chose substring to honour the "readable, not cramped"
|
||||
ethos. No reason surfaced in this conversation to revisit — leaving as-is
|
||||
for v2.
|
||||
|
||||
### 3.8 Multi-select / batch actions across hosts — **deferred, flagged as scope creep**
|
||||
- Note: this is distinct from the batch *file* selection now included in the
|
||||
SFTP browser (§3.1), which is about selecting multiple files/folders within
|
||||
one host's transfer session. This item is about running a command or
|
||||
transfer across *multiple hosts* at once — the report itself raises this as
|
||||
possible scope creep. Recommend leaving it out of v2 — that's a meaningfully
|
||||
different tool shape (more like an orchestration tool than a picker) and
|
||||
deserves its own dedicated design pass if wanted later, not a bolt-on here.
|
||||
|
||||
### 3.9 Connection history/analytics beyond recents — **small addition**
|
||||
- Add a **last-connected timestamp** shown in the preview pane (already
|
||||
tracking recency internally for sort — this just surfaces it visually).
|
||||
- Per-host notes: **deferred** — no clear use case surfaced yet; easy to add
|
||||
later without restructuring anything, so not worth the effort now.
|
||||
|
||||
---
|
||||
|
||||
## v2 Scope Summary
|
||||
|
||||
**Building now:**
|
||||
1. Alphabetical sort mode
|
||||
2. `dial keygen` subcommand (cross-platform, ed25519-default, config-append)
|
||||
3. In-app SFTP file browser (`t` to open, `pkg/sftp`-based — local/remote
|
||||
listings, batch file/folder selection, recursive directory transfers,
|
||||
same-session resume-on-failure, aggregate progress, overwrite prompts)
|
||||
4. Tag grouping toggle + colour-coded tag pills
|
||||
5. YubiKey gate extended to cover the new SFTP browser
|
||||
6. Last-connected timestamp in preview pane
|
||||
|
||||
**Explicitly deferred (not this pass):**
|
||||
- Resume that survives closing/restarting dial (crash-persistent resume)
|
||||
- Idle-timeout gate option
|
||||
- Distribution/signing/CI/package managers
|
||||
- Multi-select/batch actions *across hosts* (§3.8) — distinct from in-browser
|
||||
batch file selection, which is now in scope
|
||||
- Per-host notes
|
||||
- Fuzzy search
|
||||
|
||||
---
|
||||
|
||||
## Notes for Claude Code
|
||||
- The SFTP browser is a genuinely new subsystem (`internal/sftp` or similar),
|
||||
not a reuse of `internal/connect`'s exec-handover pattern — it needs its own
|
||||
connection lifecycle via `pkg/sftp`/`golang.org/x/crypto/ssh` since dial
|
||||
stays in the foreground driving the UI, rather than handing off the process
|
||||
like the SSH connect flow does.
|
||||
- `dial keygen` is a new top-level subcommand alongside the existing
|
||||
`yubikey enable/disable/status/recovery`, `version`, `help` — not part of
|
||||
the TUI picker's keybindings.
|
||||
- Confirm with the user during implementation: tag-grouping as toggle vs sort
|
||||
mode (§3.6), if it's not obvious once in the code.
|
||||
- Given the SFTP browser is now the largest single piece of v2, worth
|
||||
sequencing it last (after alphabetical sort, keygen, tag grouping, last-
|
||||
connected timestamp) so the smaller wins land first and the browser gets
|
||||
full attention/testing time.
|
||||
Reference in New Issue
Block a user