yconn 1.13.0

SSH connection manager for teams and DevOps environments
# Tasks: yconn

- [x] **Add `yconn install` command to install project connections into a target layer** [cli] M
  - Acceptance: (1) `yconn install` reads the project `.yconn/connections.yaml` discovered by the upward walk from the current directory and copies all connections into the target layer file (`~/.config/yconn/connections.yaml` by default, `/etc/yconn/connections.yaml` with `--layer system`); (2) for each connection not present in the target file, the entry is appended and `Writing: <path>` is printed to stdout; (3) for each connection whose name already exists in the target file, the user is prompted interactively `Connection '<name>' already exists — update? [y/N]`; if the user answers `y` or `Y` the entry is replaced and `Updating: <path>` is printed; if the user answers anything else the connection is skipped and `Skipping: <name> (already exists)` is printed; (4) when no project config is found the command exits with a clear error; (5) the `--layer` flag accepts `user` (default) or `system` only — `project` is rejected with a clear error since installing into the project layer is circular; (6) unit tests in `src/commands/install.rs` cover: new connections appended and `Writing:` line emitted, existing connection with `y` answer replaced and `Updating:` line emitted, existing connection with `N` answer skipped and `Skipping:` line emitted, missing project config returns error, `--layer project` returns error; (7) a functional test in `tests/functional.rs` writes a project config with two connections (`alpha`, `beta`), runs `yconn install`, and asserts both appear in the user layer file; a second functional test pre-populates the user layer with `alpha`, runs `yconn install` with `y` stdin for the update prompt, and asserts `alpha` is updated and `beta` is appended; `make test` passes
  - Depends on: Restructure `yconn ssh-config` into subcommands: `install`, `print`, `uninstall`, `disable`, `enable`
  - Modify: src/cli/mod.rs, src/main.rs, src/commands/mod.rs, tests/functional.rs
  - Create: src/commands/install.rs
  - Reuse: src/commands/add.rs:entry_exists (detect existing connection name in target file content), src/commands/add.rs:insert_connection (append new connection block under `connections:`), src/commands/add.rs:build_entry (construct YAML block from Connection fields), src/commands/add.rs:set_private_permissions (apply 0o600 after writing), src/commands/add.rs:layer_arg_to_layer (convert LayerArg to Layer), src/commands/add.rs:layer_path (resolve target directory path from Layer), src/config/mod.rs:LoadedConfig::project_dir (already-discovered `.yconn/` path from upward walk — use as the source directory), src/config/mod.rs:Connection (iterate `cfg.connections` to get all source entries with their field values), src/display/mod.rs:Renderer (route all output through renderer; use `renderer.print_line` or equivalent for `Writing:`/`Updating:`/`Skipping:` messages), tests/functional.rs:TestEnv (write_project_config, write_user_config, run)
  - Risks: `build_entry` in `src/commands/add.rs` takes individual field arguments (`host`, `user`, `port`, etc.) rather than a `&Connection` — the install handler must destructure each `Connection` to call it, or a thin wrapper must be added; the `replace_connection` operation (for update path) does not exist yet — it must be implemented in `install.rs` by reading the target file, locating the existing entry block by name, replacing it, and writing back; the upward-walk result is available as `cfg.project_dir` (a `PathBuf` to the `.yconn/` directory) — derive the source file path as `cfg.project_dir.unwrap().join("connections.yaml")`; `--layer project` must be explicitly rejected before dispatching — check `layer_arg == Some(LayerArg::Project)` at the top of the handler and bail with a clear message; the interactive prompt for existing connections must read from stdin line by line — use the same `prompt` / `BufRead` pattern as `add.rs` and `user.rs` so the handler is testable with injected input; connections loaded via `cfg.connections` are the fully merged active set — the install command must re-read only the project-layer connections from `cfg.project_dir` rather than iterating all merged connections, to avoid accidentally installing user- or system-layer entries into the target

- [x] **Improve yconn install progress output to show connection name per write/skip** [cli] S
  - Acceptance: (1) for each new connection written, `run_impl` prints `Writing: connection <name> -> <filepath>` instead of `Writing: <filepath>`; (2) for each connection skipped (user answered N or defaulted), `run_impl` prints `Skipping: connection <name> -> <filepath> (already up to date)` instead of `Skipping: <name> (already exists)`; (3) for each connection updated (user answered y), `run_impl` prints `Updating: connection <name> -> <filepath>` instead of `Updating: <filepath>`; (4) unit tests in `src/commands/install.rs` are updated so `test_new_connections_appended_and_writing_emitted` asserts the output contains `Writing: connection alpha ->` and the target path, `test_existing_connection_y_replaces_and_updating_emitted` asserts `Updating: connection alpha ->` and the target path, and `test_existing_connection_n_skipped_and_skipping_emitted` asserts `Skipping: connection alpha ->` and the target path; (5) a functional test in `tests/functional.rs` runs `yconn install` with two connections and asserts stdout contains `Writing: connection alpha ->` and `Writing: connection beta ->`; `make test` passes
  - Depends on: Add `yconn install` command to install project connections into a target layer
  - Modify: src/commands/install.rs, tests/functional.rs
  - Create: none
  - Reuse: src/commands/install.rs:run_impl (the three `writeln!` call sites at lines 134, 137, and 146 are the only change sites — update the format strings to include the connection name and `->` separator before the path), src/commands/install.rs:extract_connection_names (unchanged — `name` is already in scope at each writeln! site inside the `for name in &connection_names` loop)
  - Risks: the three existing unit tests each assert on specific output strings (`Writing: {target_str}`, `Updating: {target_str}`, `Skipping: alpha (already exists)`) — all three assertions must be updated to match the new format or the tests will fail; any functional test in `tests/functional.rs` that currently asserts exact `Writing:` or `Skipping:` output from `yconn install` must be audited and updated; the `->` separator must be a literal ` -> ` (space-arrow-space) consistent with the format used by `yconn ssh-config install` output elsewhere in the codebase — verify no other install output format uses a different separator before committing to this choice

- [x] **Show user variable resolution table above connections in yconn list output** [cli] M
  - Acceptance: `yconn list` prints a `Users:` table before the connections table (separated by a blank line) whenever one or more `${...}` placeholders appear in any active connection's fields; each placeholder row shows its resolved value and source (layer + path) or `[unresolved]` with a `→ yconn users add --user KEY:VALUE` hint; the Users table is omitted entirely when no placeholders exist; connections table USER column preserves the raw `${variable}` syntax unchanged; unit tests in `src/commands/list.rs` cover: placeholder present and resolved shows correct value and source, placeholder present and unresolved shows `[unresolved]` and fix hint, no placeholders produces no Users table output, mixed resolved and unresolved placeholders both appear; `make test` passes
  - Depends on: Improve yconn install progress output to show connection name per write/skip
  - Modify: src/commands/list.rs, src/display/mod.rs
  - Create: none
  - Reuse: src/config/mod.rs:LoadedConfig::users (resolve placeholder keys against this map), src/config/mod.rs:expand_user_field (call with empty inline_overrides to detect unresolved tokens without mutating connection data), src/config/mod.rs:UserEntry (source layer and path for the resolved-value SOURCE column), src/display/mod.rs:UserRow (reuse directly — key=variable name without `${}`, value=resolved value or `[unresolved]`, source=layer+path or fix hint, shadowed=false), src/display/mod.rs:Renderer::user_list (renders the Users table; called before `list` when rows is non-empty), src/display/mod.rs:render_user_list (already formats VARIABLE/VALUE/SOURCE columns with `${key}` display — reuse without modification)
  - Risks: scanning all active connection fields for `${...}` tokens must cover all fields that support template expansion (primarily `user:`, but verify whether `host:` or `key:` can also carry placeholders — if yes, those must be scanned too); `render_user_list` displays each key as `${key}` already, so the VARIABLE column output is correct without changes; the Users table must use `Renderer::user_list` rather than a new renderer method to avoid duplicating the table-formatting logic — verify `user_list` accepts `&[UserRow]` (it does, per the existing signature); the blank line between the Users table and the connections table must be emitted by `list::run` (via `println!()` or a `Renderer` helper) after calling `renderer.user_list` and before calling `renderer.list`, not inside either renderer method, to keep the renderers stateless; deduplication of placeholder keys is required — if `${deploy_user}` appears in three connections it should produce only one row in the Users table; placeholder extraction must handle fields that contain multiple `${...}` tokens (e.g. a hypothetical `user: "${prefix}_${suffix}"`) by emitting one row per unique key; the `--all` flag passes shadowed connections to the renderer — decide whether placeholders from shadowed connections should appear in the Users table (safest: scan only active connections, not `all_connections`, to avoid surfacing variables from overridden entries)

- [x] **Prompt for missing user during yconn install / ssh-config install** [cli] M
  - Acceptance: (1) `yconn install` and `yconn ssh-config install` scan all project connections for `${key}` user-field tokens that are not present in `cfg.users`; when one or more unresolved keys are found the command halts before writing any files and prints a grouped prompt listing each missing key and the connection names that reference it (e.g. `Missing user variable '${t1user}' used by: conn-a, conn-b`); the user is then prompted to provide a value for each missing key following the same flow as `yconn users add` (value is written to the target layer's config via `write_user_entry`); after all missing keys are resolved the command proceeds normally; (2) when all user variables are already resolved the commands behave identically to today with no extra prompts; (3) unit tests in `src/commands/install.rs` cover: connections with unresolved `${key}` trigger prompt and supplied value is written, connections with all keys resolved skip prompting, multiple connections referencing the same missing key produce a single prompt listing all connection names; (4) unit tests in `src/commands/ssh_config.rs` cover: `run_install` with unresolved key halts and prompts, supplied value resolves the token in generated Host blocks; (5) functional tests in `tests/functional.rs` cover: `yconn install` with missing user variable prompts and writes the value then completes the install, `yconn ssh-config install` with missing user variable prompts and writes the value then generates correct Host blocks; `make test` passes
  - Depends on: Show user variable resolution table above connections in yconn list output
  - Modify: src/commands/install.rs, src/commands/ssh_config.rs, tests/functional.rs
  - Create: none
  - Reuse: src/config/mod.rs:expand_user_field (detect unresolved keys before proceeding), src/config/mod.rs:LoadedConfig::users (check which keys are already defined), src/commands/ssh_config.rs:extract_unresolved_key (extract key name from unresolved token), src/commands/user.rs:write_user_entry (write prompted value to target layer config), src/commands/user.rs:layer_path (resolve target directory for writing user entries)
  - Risks: `run_impl` in `install.rs` currently receives `project_file` and `target_file` paths but has no access to a `LoadedConfig` — it cannot call `expand_user_field` without either threading `cfg` through or re-implementing token scanning; the simplest approach is to scan for `${...}` tokens in raw YAML user fields via regex or string matching in `run_impl` and cross-reference against the target file's existing `users:` section; `run_install` in `ssh_config.rs` already calls `expand_user_field` per connection — the prompt-and-write logic must run as a pre-pass before the per-connection loop so that all missing keys are collected first and each key is prompted only once regardless of how many connections reference it; the prompted value must be persisted via `write_user_entry` to the user-layer config so subsequent runs do not re-prompt — this means the install commands gain a write side-effect on `~/.config/yconn/connections.yaml` even though their primary target may be a different file; `extract_unresolved_key` is currently `fn` (private) in `ssh_config.rs` — it must be made `pub(crate)` or moved to a shared location so `install.rs` can reuse it; the interactive prompt must read from the same `input: &mut dyn BufRead` already threaded through `run_impl` (install) and must be added to `run_install` (ssh-config) which currently uses `stdin` implicitly via `println!` — threading a `BufRead` into `run_install` requires a signature change and updating all call sites including tests

- [x] **Restructure `auth` from a string field into a structured node with `type`, `key`, and `cmd` fields** [core] L
  - Acceptance: (1) `auth` is a YAML mapping node with `type` (required: `"key"` or `"password"`), `key` (required when type=key, path to private key), and `cmd` (optional, shell command string); (2) the old flat format (`auth: key` + sibling `key:` field) is removed — this is a breaking change; (3) `Auth` is a serde-tagged Rust enum with `Key { key, cmd }` and `Password` variants; (4) `RawConn.auth` is `Option<Auth>`, `Connection.auth` is `Auth`; (5) `build_args` in `connect` uses `Auth::Key { ref key, .. }` to emit `-i` flag; (6) `yconn show` displays `Auth:`, `Key:`, and `Cmd:` (when present) lines; (7) `yconn list` shows auth type label in the AUTH column; (8) `yconn ssh-config` outputs `# auth:` comment with type label and `IdentityFile` from `auth.key()`; (9) `yconn connections add` wizard emits nested `auth:` YAML block; (10) `yconn show --dump` serializes `auth` as a nested YAML mapping; (11) `cmd` field is parsed and stored only — no execution logic; (12) all unit tests, functional tests, and example configs updated to new format; (13) `make test` passes; (14) `make lint` passes
  - Depends on: Add `yconn install` command to install project connections into a target layer
  - Modify: src/config/mod.rs, src/connect/mod.rs, src/display/mod.rs, src/commands/show.rs, src/commands/list.rs, src/commands/connect.rs, src/commands/ssh_config.rs, src/commands/add.rs, src/security/mod.rs, tests/functional.rs, config/connections.yaml, CLAUDE.md
  - Create: none
  - Reuse: src/config/mod.rs:RawConn (replace `auth: Option<String>` + `key: Option<String>` with `auth: Option<Auth>`), src/config/mod.rs:Connection (replace `auth: String` + `key: Option<String>` with `auth: Auth`), src/config/mod.rs:build_connection (map `raw.auth` directly to `conn.auth`), src/config/mod.rs:validate_connections (check `auth` is `Some`), src/connect/mod.rs:build_args (match on `Auth` enum for `-i` flag), src/display/mod.rs:ConnectionDetail (add `cmd: Option<String>` field and render it), src/commands/show.rs:DumpConn (serialize `Auth` enum as nested YAML), src/commands/add.rs:build_entry (emit nested `auth:` YAML block)
  - Risks: serde `#[serde(tag = "type")]` internally-tagged enum requires the YAML mapping to contain a `type` key — verify `serde_yaml` handles this correctly for both serialization and deserialization; all inline YAML test fixtures (~30+ in `tests/functional.rs` alone) must be updated with correct nested indentation or deserialization will fail silently; `DumpConn` in `show.rs` uses `Serialize` — the `Auth` enum must derive both `Serialize` and `Deserialize` with matching tag configuration; `build_entry` in `add.rs` constructs YAML by hand — the nested `auth:` block requires careful indentation (6 spaces for inner fields if connection fields use 4); the `security` module's credential-field scanning checks YAML mapping keys not values, so `type: password` (where "password" is a value) should not trigger a false positive — verify this

- [x] **Add identity-only connection type for ssh-config-only entries (e.g. git hosts)** [core] M
  - Acceptance: (1) a new `auth: identity` type is accepted in connection YAML with a required `key` field (path to private key); (2) `yconn ssh-config install` generates Host blocks for identity connections containing `IdentityFile <key>` and `IdentitiesOnly yes` directives; (3) `yconn connect` allows connecting to identity connections but prints a warning to stderr that the host may not support interactive SSH; (4) `yconn list` and `yconn show` display the auth type as `identity` in the AUTH column and detail view respectively; (5) `yconn connections add` wizard includes `identity` as a third option alongside `key` and `password` and prompts for `key` when selected; (6) `yconn show --dump` serializes `auth: identity` connections correctly; (7) config validation rejects `auth: identity` without a `key` field (same as `auth: key`); (8) unit tests in `src/config/mod.rs` cover: identity connection parsed correctly, identity without key rejected; unit tests in `src/connect/mod.rs` cover: `build_args` for identity auth produces `ssh -F /dev/null -i <key> user@host` (same as key auth) and warning is emitted; unit tests in `src/commands/ssh_config.rs` cover: `render_ssh_config` for identity auth emits `IdentityFile` and `IdentitiesOnly yes`; unit tests in `src/commands/add.rs` cover: wizard with identity choice emits correct YAML; functional tests in `tests/functional.rs` cover: identity connection round-trip (add, list, show, ssh-config install); `make test` passes
  - Depends on: Restructure `auth` from a string field into a structured node with `type`, `key`, and `cmd` fields
  - Modify: src/config/mod.rs, src/connect/mod.rs, src/display/mod.rs, src/commands/ssh_config.rs, src/commands/show.rs, src/commands/list.rs, src/commands/connect.rs, src/commands/add.rs, tests/functional.rs, config/connections.yaml
  - Create: none
  - Reuse: src/config/mod.rs:Auth (add `Identity { key, cmd }` variant to the serde-tagged enum), src/config/mod.rs:validate_connections (extend key-required check to cover identity type), src/connect/mod.rs:build_args (extend Auth match to handle Identity variant — emit `-i <key>` same as Key), src/commands/ssh_config.rs:render_ssh_config (extend key-emission branch to also emit `IdentitiesOnly yes` for identity auth), src/commands/add.rs:prompt_choice (add "identity" to the choices list), src/commands/add.rs:build_entry (handle identity auth type in YAML generation), src/display/mod.rs:ConnectionRow (auth field already renders as string — "identity" will display automatically), src/display/mod.rs:ConnectionDetail (auth field renders as string — "identity" will display automatically)
  - Risks: the `IdentitiesOnly yes` directive must appear after `IdentityFile` in the generated Host block — verify `render_ssh_config` emits them in the correct order; if the auth restructuring task uses `#[serde(tag = "type")]` the new `Identity` variant must use the same tag scheme — the YAML representation will be `auth: { type: identity, key: ... }` and `serde_yaml` must round-trip this correctly; `build_args` for identity auth is functionally identical to key auth (both emit `-i <key>`) but the connect handler must additionally emit a warning via `renderer.warn` — this requires threading a renderer or warning callback into the connect path, which currently has no such parameter; alternatively the warning can be emitted by the CLI command handler in `src/commands/connect.rs` before calling `connect::run`, by inspecting `conn.auth`; the `IdentitiesOnly yes` directive tells SSH to use only the specified identity file and ignore ssh-agent — this is the correct behaviour for git hosts but may surprise users who expect agent-forwarded keys to work; the warning message for `yconn connect` should clearly state "this connection is configured as identity-only (e.g. for git hosts) and may not support interactive SSH sessions"

- [x] **Extend example config/connections.yaml to showcase all documented features** [docs] S
  - Acceptance: `config/connections.yaml` includes working examples of: (1) all three `auth` types (`key`, `password`, `identity`) using the nested `auth:` mapping format with `type`, `key`, and `cmd` fields; (2) at least one connection with `auth.cmd` populated; (3) at least one connection with a `group` field (inline group tag); (4) at least one connection with a `${variable}` template in the `user` field; (5) a `users:` block with at least two entries including one that resolves a template used in a connection; (6) an uncommented `docker:` block with `image`, `pull`, and `args` fields all present; (7) at least five connections demonstrating different field combinations (with/without port, with/without link, different auth types, with/without group); (8) the file remains valid YAML that parses without error via `serde_yaml`; (9) comments are present explaining each feature for quick-start reference; no code changes, no new tests required
  - Depends on: Add identity-only connection type for ssh-config-only entries (e.g. git hosts)
  - Modify: config/connections.yaml
  - Create: none
  - Reuse: src/config/mod.rs:RawConn (field names define valid YAML keys), src/config/mod.rs:Auth (enum variants define valid auth type/key/cmd combinations), src/config/mod.rs:DockerConfig (fields define valid docker block keys)
  - Risks: the example file is loaded by some functional tests or used as a documentation reference -- verify no test imports or parses `config/connections.yaml` directly before changing it; the `cmd` field is parsed and stored only (no execution logic) so any example value is safe; ensure the `${variable}` template in the example matches a key in the `users:` block so the example is self-consistent

- [x] **Rewrite make install to build and install a distro-native package (deb or Arch) instead of raw file copy** [packaging] S
  - Acceptance: `make install` detects the distro via `/etc/os-release` (Debian/Ubuntu vs Arch), depends on `make package` to produce the `.deb` or `.pkg.tar.zst`, then runs `sudo dpkg -i` or `sudo pacman -U` as appropriate; unrecognized distros get a clear error message suggesting manual installation; `sudo` is scoped to only the package-manager invocation (the user never runs `sudo make install`); the existing `make package`, `build-deb`, and `build-pkg` targets are unchanged; manually running `make install` on a Debian-based system installs the `.deb` and on Arch installs the `.pkg.tar.zst`
  - Depends on: Extend example config/connections.yaml to showcase all documented features
  - Modify: Makefile
  - Create: none
  - Reuse: scripts/install-deps.sh:is_debian_like (distro-detection pattern using /etc/os-release, ID, ID_LIKE), scripts/install-deps.sh:is_arch_like (same pattern for Arch detection), scripts/build-deb.sh (output path convention: dist/${BINARY}_${VERSION}_amd64.deb), scripts/build-pkg.sh (output path convention: dist/${BINARY}-${VERSION}-1-x86_64.pkg.tar.zst)
  - Risks: `make install` currently accepts a `PREFIX` variable for the install path — removing raw-copy semantics is a breaking change for anyone using `PREFIX=/opt make install`; the `sudo` prompt inside a Makefile recipe may confuse users who expect `make install` to be non-interactive — the error message for unrecognized distros should also mention that `make package` can be used standalone to build packages without installing; the `.deb` and `.pkg.tar.zst` filenames are constructed from `BINARY` and `VERSION` Makefile variables which must stay in sync with the scripts; `dpkg -i` does not resolve dependencies (unlike `apt install`) — if the `.deb` declares dependencies the user may need to run `sudo apt-get install -f` afterwards, or the recipe should use `sudo apt-get install ./dist/<pkg>.deb` instead of `dpkg -i`

- [x] **Remove 'Users:' header from yconn list and rename VARIABLE column to USER in the resolution table** [cli] S
  - Acceptance: (1) `yconn list` no longer prints a `Users:` label line above the user variable resolution table — the table renders directly without a preceding header; (2) the first column header in the user variable resolution table reads `USER` instead of `VARIABLE`; (3) `yconn users show` output also reflects the `USER` column rename since it shares the same `render_user_list` renderer method; (4) all other table content, formatting, column widths, and behavior remain unchanged; (5) existing unit tests in `src/commands/list.rs` continue to pass (they test `build_user_rows` which is unaffected); (6) a unit test is added to `src/display/mod.rs` tests verifying `render_user_list` output contains `USER` as a column header and does not contain `VARIABLE`; `make test` passes
  - Depends on: Rewrite make install to build and install a distro-native package (deb or Arch) instead of raw file copy
  - Modify: src/commands/list.rs, src/display/mod.rs
  - Create: none
  - Reuse: src/display/mod.rs:render_user_list (change HEADERS constant at line 312 from `"VARIABLE"` to `"USER"`), src/commands/list.rs:run (remove the `println!("Users:");` call at line 26)
  - Risks: renaming `VARIABLE` to `USER` in `render_user_list` also affects `yconn users show` output (called via `src/commands/user.rs:show`) since both code paths share the same renderer method — this is intentional per the task but should be verified as acceptable; the `USER` column name now matches the `USER` column in the connections table rendered by `render_list` — if both tables appear in the same `yconn list` output this is fine since they are visually separated, but confirm no confusion arises from identical column names in adjacent tables; no existing tests assert on the literal strings `"Users:"` or `"VARIABLE"` so no test breakage is expected beyond needing to add the new assertion

- [x] **Fix user variable resolution in yconn install to check cfg.users instead of only scanning the target file** [cli] S
  - Acceptance: (1) `yconn install`'s `run_impl` resolves `${key}` user-field tokens against the full merged `cfg.users` map (from all layers) instead of only scanning the target file's `users:` block via `extract_existing_user_keys`; variables defined in the project layer's `users:` block or any other layer are correctly treated as resolved and do not trigger a spurious prompt; (2) `run_impl` signature changes to accept `&LoadedConfig` (or at minimum a `&HashMap<String, UserEntry>` for the merged users map) in addition to the existing file path and I/O parameters; (3) `find_unresolved_user_keys` is replaced or updated to check `cfg.users` (matching the pattern used by `collect_unresolved_keys` in `ssh_config.rs`); (4) the `extract_existing_user_keys` function is removed or demoted to a private helper since it is no longer the primary resolution mechanism; (5) unit tests in `src/commands/install.rs` cover: a project config with `${t1user}` in user field and `t1user` defined in the project file's `users:` block does NOT trigger a prompt, a project config with `${t1user}` not defined in any layer DOES trigger a prompt, a project config with `${t1user}` defined in the user layer's `users:` block does NOT trigger a prompt; (6) a functional test in `tests/functional.rs` writes a project config containing both a `users:` block with `t1user: alice` and a connection referencing `${t1user}`, runs `yconn install`, and asserts the install completes without prompting (no `Missing user variable` in stdout); `make test` passes
  - Depends on: Remove 'Users:' header from yconn list and rename VARIABLE column to USER in the resolution table
  - Modify: src/commands/install.rs, tests/functional.rs
  - Create: none
  - Reuse: src/config/mod.rs:LoadedConfig::users (merged user entries from all layers — use as the authoritative resolution source), src/commands/ssh_config.rs:collect_unresolved_keys (reference implementation that correctly checks cfg.users and inline_overrides), src/commands/ssh_config.rs:extract_all_template_keys (extract all `${key}` tokens from a user field string), src/commands/install.rs:prompt_missing_user_keys (unchanged — still prompts for truly unresolved keys after the corrected check), src/commands/install.rs:extract_connection_names (unchanged — still extracts connection names from raw project YAML), src/commands/install.rs:extract_user_field (unchanged — still extracts user field value per connection from raw YAML)
  - Risks: `run_impl` currently takes only `(project_file, target_file, input, output)` — adding `cfg: &LoadedConfig` changes the function signature and requires updating all call sites including the three unit test helpers (`run_with_stdin`) which construct test scenarios without a full `LoadedConfig`; the simplest approach for tests is to build a minimal `LoadedConfig` with only the `users` field populated, but `LoadedConfig` may have required fields (layers, connections, etc.) that need dummy values; alternatively, pass `&HashMap<String, UserEntry>` instead of the full config to keep the test surface small; the `run` entry point in `install.rs` already has access to `cfg: &LoadedConfig` (line 22) but does not pass it to `run_impl` — threading it through is straightforward; `extract_existing_user_keys` scanned raw YAML text which worked for the target file but missed users defined in other layers — after this fix, the target file's `users:` block is still relevant for `prompt_missing_user_keys` (which writes to the target file) but not for determining whether a key is resolved

- [x] **Rename auth.cmd field to auth.generate_key** [core] S
  - Acceptance: (1) the `cmd` field in `Auth::Key` and `Auth::Identity` enum variants is renamed to `generate_key` in both Rust code and YAML serialization (the YAML field name changes from `cmd` to `generate_key`); (2) the `Auth::cmd()` convenience method is renamed to `Auth::generate_key()` and all call sites are updated; (3) `yconn show` displays `Generate Key:` instead of `Cmd:` when the field is present; (4) `yconn show --dump` serializes the field as `generate_key` in YAML output; (5) `ConnectionDetail.cmd` field is renamed to `ConnectionDetail.generate_key`; (6) all unit tests, functional tests, example config (`config/connections.yaml`), and CLAUDE.md documentation are updated to use `generate_key` instead of `cmd`; (7) this is a breaking change to the config format; (8) `make test` passes; (9) `make lint` passes
  - Depends on: Fix user variable resolution in yconn install to check cfg.users instead of only scanning the target file
  - Modify: src/config/mod.rs, src/display/mod.rs, src/commands/show.rs, src/commands/connect.rs, src/commands/ssh_config.rs, src/connect/mod.rs, tests/functional.rs, config/connections.yaml, CLAUDE.md
  - Create: none
  - Reuse: src/config/mod.rs:Auth (rename `cmd` field to `generate_key` in `Key` and `Identity` variants, rename `cmd()` method to `generate_key()`), src/display/mod.rs:ConnectionDetail (rename `cmd` field to `generate_key`), src/display/mod.rs:render_show (update `pad("Cmd:", LW)` to `pad("Generate Key:", LW)`), src/commands/show.rs:run (update `conn.auth.cmd()` call to `conn.auth.generate_key()`)
  - Risks: the serde `skip_serializing_if` and `default` attributes on the `cmd` field must be preserved on the renamed `generate_key` field to maintain optional-field behavior; CLAUDE.md references `cmd` in three places (line 120 example YAML, line 169 auth field table, line 172 `auth.cmd` row) — all must be updated; `config/connections.yaml` references `cmd` in comments and YAML keys at lines 45-46, 55, 82-83, 90 — all must be updated; the `pad("Cmd:", LW)` call in `display/mod.rs` line 232 produces a padded label — `"Generate Key:"` is longer than `"Cmd:"` so verify it does not exceed the `LW` constant and misalign the output; no `add.rs` changes are needed since the add wizard does not currently prompt for or emit the `cmd` field

- [x] **Commit HISTORY-TASKS.md and document it in CLAUDE.md as an archive for completed tasks** [docs] S
  - Acceptance: (1) the existing untracked `HISTORY-TASKS.md` file at the repo root is committed as-is on a feature branch via PR (no content edits to the file); (2) `CLAUDE.md` gains a new section describing `HISTORY-TASKS.md`: what it is (an archive of previously completed tasks moved out of `TASKS.md`), why it exists (to keep `TASKS.md` small enough to load into the Claude context window during normal work without also loading the full task history), and the maintenance workflow (when `TASKS.md` grows too large, completed `[x]` entries are moved verbatim to `HISTORY-TASKS.md` preserving their full acceptance/depends/modify/create/reuse/risks sub-bullets); (3) no code changes, no tests required; `make lint` passes
  - Depends on: Rename auth.cmd field to auth.generate_key
  - Modify: CLAUDE.md
  - Create: none (HISTORY-TASKS.md already exists untracked and is committed verbatim)
  - Reuse: none
  - Risks: the new CLAUDE.md section should be placed where it is discoverable alongside other project-meta guidance (e.g. near the end after "Non-goals" or as a dedicated "Task Tracking" section) without disrupting the existing structural flow; the workflow description must not prescribe a specific line-count threshold for archival since that is a judgment call — phrase it as "when TASKS.md grows too large to comfortably load into context"; ensure the committed `HISTORY-TASKS.md` is not accidentally modified during this task (commit it verbatim first, then update CLAUDE.md in a separate commit on the same branch, or in the same commit if preferred)