Skip to content

Conversation

@OleksandrKucherenko
Copy link

@OleksandrKucherenko OleksandrKucherenko commented Dec 27, 2025

Summary

Implement comprehensive SSH key management (issue #91) with a new 2-panel UI design, ssh-agent integration, and per-repository Git SSH configuration.

New Features

SSH Key Management Panel

  • Dedicated SSH Agent panel with load/unload key operations (l/u keys)
  • Config Keys panel showing all SSH keys from discovered locations
  • Visual indicators: ● (loaded), 🔒 (encrypted), ⚠ (missing .pub)
  • SSH key details view with type, size, fingerprint, comment
  • Edit SSH key comments (C key)

2-Panel Layout

  • Servers panel + SSH Agent panel (Tab/Shift+Tab/mouse to navigate)
  • SSH key details shown in server panel when key is configured
  • Git repo info displayed when in a Git repository

Git SSH Integration

  • Configure Git SSH key per repository (G key)
  • Select local or global scope
  • Display push remote URL in git info panel
  • Clear configuration option

Key Discovery

  • SSH keys discovered from config files, not just ~/.ssh/
  • Fingerprint-based key operations for reliable identification
  • Keychain/ssh-agent status displayed in server list

This addresses the common scenario of managing multiple SSH identities (company, personal, vendor accounts) by making it easy to configure which key Git should use for a specific repository. By controlling which SSH key is loaded into ssh-agent we also can control which SSH key will be used for git repository commits/push, in addition to hardcoded fix of the repository.

Configure Git Repo SSH Key
image
Show Keychain/ssh-add Keys
image
Edit SSH Key Comment
image

@OleksandrKucherenko OleksandrKucherenko marked this pull request as draft December 27, 2025 13:22
@OleksandrKucherenko OleksandrKucherenko marked this pull request as ready for review December 27, 2025 16:49
This commit implements the feature requested in issue Adembc#91 to help users
configure which SSH key Git should use for repository operations.

Changes:
- Added GitService with methods to detect git repositories, list SSH keys,
  and configure git to use specific SSH keys
- Added SSHKey struct to represent SSH key metadata
- Created GitSSHSetup UI modal for interactive SSH key selection
- Added keyboard shortcut 'G' to trigger Git SSH setup modal
- Updated status bar to show the new Git SSH shortcut
- Integrated git service into TUI application

The feature allows users to:
- Detect if they are in a git repository
- List available SSH keys from ~/.ssh/
- Select an SSH key to use for git operations
- Configure git via 'git config core.sshCommand' at local or global scope
- View current SSH configuration for the repository

This addresses the common scenario of managing multiple SSH identities
(company, personal, vendor accounts) by making it easy to configure
which key Git should use for a specific repository.

fix: discover SSH keys from config files, not just ~/.ssh/

This fix addresses the issue where SSH keys stored outside of ~/.ssh/
directory were not being discovered by the Git SSH setup feature.

Changes:
- Updated ListSSHKeys to accept ServerRepository parameter
- Added logic to extract IdentityFile paths from SSH config
- Keys from SSH config are now the primary source
- ~/.ssh/ directory is scanned as a secondary source
- Refactored complex function into smaller helpers to reduce cyclomatic complexity
- Added proper error handling for missing ~/.ssh/ when config keys exist

The feature now correctly discovers SSH keys from:
1. SSH config file (primary source) - reads IdentityFile directives
2. ~/.ssh/ directory (secondary source) - scans for additional keys

This ensures that SSH keys stored in custom locations (e.g., /home/user/workspace/_keys_/)
are properly discovered and can be configured for Git operations.

feat: add keychain/ssh-agent integration and git repo indicator

This commit enhances the Git SSH key configuration feature with:

1. Keychain/ssh-agent integration:
   - Detect which SSH keys are currently loaded in ssh-agent/keychain
   - Mark loaded keys with "in agent" indicator (green)
   - Sort keys to show agent-loaded keys first
   - Added GetLoadedAgentKeys() method to check ssh-add -l

2. Git repository indicator panel:
   - New GitInfo panel displays when in a git repository
   - Shows repository name and SSH configuration status
   - Appears at bottom of server list (4 lines)
   - Prompts user to press 'G' if SSH not configured

3. Enhanced UI feedback:
   - Keys in agent shown with [green](in agent)[-] tag
   - Count of keys loaded in agent displayed in setup dialog
   - Keys without .pub file shown with [red](no .pub)[-]
   - Encrypted keys shown with [yellow](encrypted)[-]

4. Improved key discovery:
   - Keys from SSH config (primary source) checked against agent
   - Keys from ~/.ssh/ (secondary source) also checked
   - Full key path matching against ssh-add output

This addresses the keychain integration requirements from issue Adembc#91,
making it easier to see which keys are ready to use and providing
better visibility when working in git repositories.

feat: add "Clear Configuration" button to reset Git SSH config

This commit adds the ability to reset Git SSH configuration to default
with a single click from the UI.

Changes:
1. Added ClearGitSSHConfig() method to GitService
   - Supports clearing local, global, or both configurations
   - Handles "key not found" gracefully (exit code 5)
   - Uses `git config --unset core.sshCommand`

2. Added scope constants (ScopeLocal, ScopeGlobal, ScopeBoth)
   - Exported constants for consistent scope handling
   - Used across service and UI layers

3. Enhanced Git SSH Setup UI
   - "Clear Configuration" button appears when config exists
   - Confirmation modal with three options:
     * Cancel - return to setup
     * Clear Local - remove repository-level config only
     * Clear Both - remove both local and global configs
   - Success/error modals with clear feedback

User Experience:
- Press 'G' to open Git SSH setup
- If SSH config exists, "Clear Configuration" button is shown
- Click to confirm which scope to clear
- Git reverts to default SSH behavior (ssh-agent, ~/.ssh/config, default keys)

This addresses the user request to easily reset Git configuration
back to default without running manual git commands.

feat: display keychain-loaded SSH keys in servers list

Add virtual server entries for SSH keys loaded in ssh-agent/keychain.
These entries appear in the main servers list with a "keychain" tag and
are read-only (cannot be edited or deleted).

Changes:
- Add GetKeychainServers() method to GitService that parses ssh-add -L
- Modify ServerService to accept GitService dependency
- Update ListServers() to merge keychain servers with repository servers
- Add isKeychainServer() helper function in UI handlers
- Guard edit/delete operations to prevent modification of keychain servers
- Display error message when attempting to edit/delete keychain servers

Addresses issue Adembc#91 comment about showing keychain-loaded keys in the list.

fix: linter error and Git info panel refresh issues

Fix two issues:

1. Linter error (prealloc): Pre-allocate servers slice with capacity
   in GetKeychainServers() to avoid prealloc lint error.

2. Git info panel not refreshing: Update handleModalClose() to call
   updateGitInfoPanel() so the panel refreshes after Git SSH config
   is cleared, showing the correct status instead of stale "SSH configured".

This ensures the Git Repository panel displays accurate information
after configuration changes.

feat: improve keychain server display and prevent invalid operations

Improvements to keychain server handling:

1. Display format: Show full key info like "rsa:4096 SHA256:5NUhY... user@email"
   - Parse ssh-add -l output (fingerprint list) instead of -L
   - Include key type, size, truncated fingerprint, and comment
   - Makes keychain entries clearly distinguishable from regular servers

2. Prevent invalid operations on keychain servers:
   - SSH connection: Show informative message that keys are auto-used
   - Ping: Cannot ping virtual keychain entries
   - Copy SSH command: Not applicable for keychain keys
   - Port forwarding: Cannot forward through keychain keys
   - Stop forwarding: Cannot stop non-existent forwards

All operations now check isKeychainServer() and display appropriate
error messages, preventing app hangs and confusing behavior.

Addresses user feedback about app hanging on Enter and unclear display.

feat: add keychain visibility toggle (K key)

Add opt-in visibility toggle for keychain-loaded SSH keys:

1. Toggle behavior:
   - Press 'K' to toggle keychain keys visibility
   - Default: hidden (keychain keys not shown)
   - When enabled: keychain keys appear in servers list
   - Status message shows current state: "Keychain keys: visible/hidden"

2. Implementation:
   - Add showKeychainKeys boolean field to TUI
   - Filter keychain servers in refreshServerList based on toggle
   - Add handleKeychainToggle() to flip state and refresh
   - Bind 'K' key to toggle handler
   - Update status bar to show 'K Keychain' command

3. Design rationale:
   - Keychain keys shown only when explicitly toggled on
   - Prevents clutter in normal server list
   - Users can enable when needed for reference
   - Maintains view-only nature (no edit/delete/SSH operations)

This addresses user feedback about keychain keys needing opt-in visibility
rather than always being displayed.

feat: add SSH key management service layer

Add foundational service layer for SSH key discovery and management:

1. Domain model (domain/sshkey.go):
   - SSHKey struct with comprehensive key information
   - Fields: Path, Name, Comment, Type, Size, Fingerprint
   - Status flags: LoadedInAgent, IsEncrypted, HasPublicKey
   - Source tracking: "config" or "agent"

2. Service interface (ports/services.go):
   - ListAllSSHKeys() - List all SSH keys from config + agent
   - LoadKeyToAgent() - Load key into ssh-agent
   - UnloadKeyFromAgent() - Remove key from ssh-agent

3. Service implementation (git_service.go):
   - Discover keys from SSH config IdentityFiles
   - Scan ~/.ssh/ directory for additional keys
   - Parse ssh-add -l output to identify loaded keys
   - Match keys by fingerprint to determine agent status
   - Parse key files to detect type and encryption
   - Use ssh-keygen to extract fingerprints
   - Sort keys: loaded first, then alphabetically

4. Load/Unload operations:
   - ssh-add <keyfile> for loading (interactive passphrase)
   - ssh-add -d <keyfile> for unloading

Preparation for SSH Keys panel UI feature. Service layer is complete
and ready for UI integration.

fix: address linter warnings in SSH key parsing

Fix gosec and gocritic linter warnings:

1. Add #nosec comments for validated file reads:
   - Reading SSH private key files from config
   - Reading public key files derived from private keys
   - ssh-keygen command with validated paths

2. Refactor if-else chains to switch statements:
   - Key type detection from ssh-add output
   - Private key format detection
   - Modern OpenSSH key type inference

All file paths are validated before use - either from SSH config
or scanned from ~/.ssh directory.

feat: add SSH Keys panel to TUI (WIP)

Add dedicated SSH Keys panel alongside Servers panel:

1. New UI Components:
   - SSHKeysList: List component for SSH keys with status indicators
   - SSHKeyDetails: Details panel showing key info and commands
   - Format: "[●] 🔒 keyname (type:size)" - green dot for loaded keys

2. TUI Layout Changes:
   - Left panel now contains: SearchBar, Servers, SSH Keys, Git Info
   - Right panel dynamically shows either ServerDetails or SSHKeyDetails
   - Removed keychain toggle (keys now in separate panel always visible)

3. Key Discovery:
   - Load SSH keys on startup using ListAllSSHKeys()
   - Show keys from SSH config + ~/.ssh/ + ssh-agent
   - Display loaded status, encryption, and public key availability

4. Filter Changes:
   - Remove ALL keychain servers from Servers list
   - They're now only in SSH Keys panel (no mixing)

Still TODO:
- Tab navigation between Servers and SSH Keys panels
- Load/Unload key handlers (l/u keys)
- Update status bar to reflect new commands
- Focus management and visual feedback

Build succeeds. Next phase: Add Tab navigation and load/unload functionality.

fix: remove redundant sprintf in SSH keys list formatting

Remove redundant fmt.Sprintf for key.Type since it's already a string.
Addresses gocritic linter warning.

feat: add Tab navigation between Servers and SSH Keys panels

- Add Tab key handler to switch focus between panels
- Implement visual feedback with border color changes (blue for active, gray for inactive)
- Update j/k navigation to work with both Servers and SSH Keys panels
- Add updatePanelBorders() and updateRightPanel() methods
- Dynamically switch right panel details based on focus

feat: implement load/unload SSH key handlers

- Add 'l' key to load selected SSH key into ssh-agent
- Add 'u' key to unload selected SSH key from ssh-agent
- Keys only respond to l/u when SSH Keys panel is focused
- Suspend TUI during load to allow passphrase entry
- Automatically refresh SSH keys list after load/unload
- Show status messages for success/failure
- Add refreshSSHKeysList() helper method

feat: update status bar with new SSH key commands

- Add 'Tab' to switch between Servers and SSH Keys panels
- Add 'l/u' for Load/Unload SSH key operations
- Remove obsolete 'K Keychain' command

feat: add Shift+Tab and mouse click support for panel navigation

- Add Shift+Tab (KeyBacktab) support to switch between panels
- Add mouse click handlers to both Servers and SSH Keys panels
- Clicking on a panel switches focus to that panel
- Both Tab and Shift+Tab toggle between the two panels

feat: add separate methods for config and agent SSH keys

- Add ListSSHKeysFromConfig() to get keys from SSH config files and ~/.ssh directory
- Add ListSSHKeysFromAgent() to get keys currently loaded in ssh-agent
- Add constants for SSH key types and file names to fix linter warnings
- Convert if-else chains to switch statements per linter recommendations
- These methods enable separate panels for config keys vs agent keys

feat: split SSH keys into separate Config and Agent panels

- Rename sshKeysList to sshConfigKeysList for config-based keys
- Add sshAgentKeysList for keys loaded in ssh-agent
- Update TUI to support 3 panels: Servers, SSH Keys (Config), SSH Agent
- Tab/Shift+Tab cycle through all 3 panels (0→1→2→0)
- Update navigation (j/k) to work with all panels
- Update mouse click handlers for all 3 panels
- Update load/unload handlers to get key from correct panel
- Refresh both key lists separately after load/unload
- Right panel shows server details for panel 0, key details for panels 1-2
- Border colors highlight active panel

feat: consolidate to 2-panel layout and add SSH key details to server panel

- Remove SSH Keys (Config) panel, keeping only Servers and SSH Agent panels
- Update panel navigation to support 2-panel layout (Tab/Shift+Tab cycling)
- Remove GetKeychainServers() and all keychain virtual server code
- Enhance server details to show SSH key information (path, type, size, status, commands)
- Update ServerDetails to fetch and display key details for configured identity files

This simplifies the UI by consolidating SSH key information directly into
the server details panel, eliminating the need for a separate config keys panel.

fix: use fingerprint-based removal for unloading SSH keys from agent

- Change UnloadKeyFromAgent to accept fingerprint instead of keyPath
- Use `ssh-add -d -E sha256 <fingerprint>` for reliable key removal
- Keys loaded in ssh-agent may not have the original file path available
- Strip SHA256:/MD5: prefix from fingerprint before passing to ssh-add
- Update handler to pass key.Fingerprint instead of key.Path

This fixes the "Failed to unload key: exit status 1" error that occurred
when trying to remove keys from ssh-agent using file paths.

fix: load/unload ssh-add logic

feat(ui): display push remote URL in git info panel

- Add GetPushRemoteURL service method to retrieve remote name and URL
- Prefer "origin" remote, falling back to first available push remote
- Update git info display to show formatted remote URL on first line
- Add visual highlighting for remote name and URL
- Enhance panel focus with selected background color changes

feat(ssh): add key comment editing functionality

Implement ability to edit SSH key comments directly from the UI using the
Shift+C key binding. This allows users to update key comments stored in
public key files without manual command-line operations.

The feature includes:
- New modal dialog for editing key comments with validation
- Integration with GitService for comment updates via ssh-keygen
- Automatic handling of file permissions for read-only keys
- Support for both server keys and agent-loaded keys
- Visual feedback showing the comment field in details panels

This resolves issue Adembc#91 by providing a complete UI workflow for managing
SSH key metadata.

fix(ui): resolve thread safety issues in SSH key loading handlers

- Remove unnecessary goroutines from handleLoadKey and handleLoadServerKey
- Suspend is blocking, allowing synchronous execution without UI freezing
- Fix potential race conditions by executing UI updates on main thread
- Add tilde expansion in getSSHKeyForServer to match identity files with home directory paths
- Update status bar help text to include comment editing shortcut

fix(ssh): handle encrypted key passphrase prompts when editing comments

- Add TUI suspension for encrypted SSH keys to allow passphrase input
- Connect stdin/stdout/stderr to ssh-keygen command for interactive prompts
- Improve encryption detection by checking for "bcrypt" cipher indicator
- Propagate encryption and public key status when merging agent keys
- Update help text formatting in server details view
- Change warning icon for missing public keys
@OleksandrKucherenko OleksandrKucherenko force-pushed the claude/implement-issue-91-okEI3 branch from cdc8b4f to fa46cf7 Compare December 27, 2025 18:10
@OleksandrKucherenko
Copy link
Author

@Adembc please review and approve

@OleksandrKucherenko
Copy link
Author

Please review and approve: @tan9 @rtuszik @adamab48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants