# Changelog ## [Unreleased] — 2025-01-31 ### Fixed - TUI host list footer now includes `Ctrl+E:edit` - New hosts/keys/snippets created via TUI form now get UUID + timestamps (`CreatedAt`, `UpdatedAt`) - Storage layer auto-generates UUID + timestamps on save if empty (defense-in-depth) - **host_form_tab.go**: Space/Left/Right keys now passthrough to text input when not on auth type field (fixes typing spaces and cursor navigation) - **tui.go**: Key and snippet list tabs now properly refresh after save/delete (mirrors host flow) - **tabs.go**: WindowSizeMsg now forwarded to ALL tabs, not just the active one (fixes stale dimensions after resize) ### Changed - All tab footers are now context-specific (no global status bar) - SFTP browser uses full-width panes (`t.width/2`) - **sftp_browser_tab.go footer**: Added `Enter/→:open`, `←/Backspace:up` - **form tabs footer**: Added `Shift+Tab:prev`, `↑↓:nav` ### Responsive Layout (Done) - **NEW `responsive.go`**: Shared helpers (`wrapFooter`, `adaptiveSidePad`, `clampWidth`, `truncateStr`) - Footer auto-wraps to multi-line when terminal is narrow (full labels preserved) - Box width clamped to terminal width — no more overflow - Host/key/snippet rows truncated with `…` when names are too long; compact row format on narrow screens - SFTP panes stack vertically when terminal < 50 cols - Tab bar truncates tab names on overflow - Form contentW minimum lowered from 50 to 30 for mobile (Termux) - Fixed: key_list_tab & snippet_list_tab were dropping last data row (off-by-one in `rows[:len(rows)-1]`) - Fixed: SFTP pane width — lipgloss `Width(n)` renders `n+2` chars (border extra). halfW now `t.width/2-2` to compensate. File names truncate to `…` within inner text area. Filter line also truncated. - Verified: zero overflow at 80, 60, 50 (side-by-side), 40, 30 cols (stacked) ### SFTP Stacked Mode Fix (Done) - Stacked mode (<50 cols) now renders only the active pane (Local or Remote) - Added pane indicator bar: `[Local] Remote` with active pane highlighted - Side-by-side mode (≥50 cols) unchanged — both panes remain visible - Tab key now visually switches between panes on narrow terminals - Default active pane: Local (was Remote) - Border height fix: pane uses fixed `Height(maxH)` — border no longer shrinks/grows when scrolling - `renderPane` now takes `maxH` parameter to control content area height - Stacked: `maxH = t.height - 6`; Side-by-side: `maxH = t.height - 5` - Breakpoints: compact (<60), medium (60–100), wide (≥100) ### SFTP Performance Fix - Added directory listing cache (`dirCache map[string][]os.FileInfo`) to each pane - Cache hit: instant navigation (no disk/network call) - Cache miss: read from disk/SFTP, store in cache - `R` key now clears cache for current directory and forces re-read - Cache is per-path: navigating to a previously visited folder is instant ### SFTP File Transfer Progress Bar - Added progress bar during upload/download: `[████████░░░░] 67% 12.3MB/18.5MB` - `progressWriter` wraps `io.Writer` and reports bytes transferred - Footer shows live progress during transfer - Auto-refresh destination pane after transfer completes - Success message shown for 1 second before clearing ### SFTP Auto-Refresh Fix - Added `program *tea.Program` field to SFTPBrowserTab and Model - `SetProgram()` method allows goroutines to send messages to Bubble Tea - After transfer: `t.program.Send(sftpRefreshMsg{})` triggers re-render - Model stores program reference, passes it to SFTP tab on creation ### SFTP Bug Fixes - Auto-refresh: clear dirCache before refresh after transfer/delete/mkdir - Cache was stale after file operations — old entries returned instead of fresh - Filter mode: allow command keys (c, d, n, r) and navigation (↑↓, Tab) to pass through - Filter mode no longer blocks transfer, delete, mkdir, or pane switch - Delete: clear cache + trigger re-render after file removal - Mkdir: clear cache + trigger re-render after directory creation ### SFTP Rename Feature - Added `M` key to rename files and directories - Rename mode: shows input with current name, type new name, Enter to confirm - Works on both Local and Remote panes - Clears cache and refreshes after rename - Footer updated: `M:rename` added - Filter mode allows `M` to pass through ## v1.0.0 (2025-01-30) ### Added - Initial release of Hostkeeper SSH/SFTP management tool - **Host management**: CRUD operations for SSH hosts (add, list, edit, delete) - **SSH connections**: Native SSH (default) and Go SSH direct mode - **TUI interface**: Interactive host browser using Bubble Tea - **Export/Import**: JSON export and import with merge/replace strategies - **Cross-platform builds**: Support for Linux, macOS, Windows, Termux - **Shell completion**: Bash, Zsh, Fish, and PowerShell support - **Configuration**: File-based credential storage with `0600` permissions - **Tag and group**: Host categorization with tags and groups - **Search and filter**: Filter hosts by group, tag, or text search ### Commands - `hostkeeper add` — Add SSH hosts (interactive and flag-based) - `hostkeeper list` — List hosts with table/JSON output - `hostkeeper connect` — Connect to hosts with custom timeout - `hostkeeper edit` — Edit host configurations - `hostkeeper delete` — Delete hosts with confirmation - `hostkeeper export` — Export data to JSON - `hostkeeper import` — Import data with merge/replace - `hostkeeper tui` — Terminal user interface - `hostkeeper completion` — Shell completion generation - `hostkeeper version` — Version information ### Technical - Cobra CLI framework for command structure - Bubble Tea TUI with keyboard navigation - Go SSH client for direct connections - Comprehensive test suite with unit and integration tests - Build automation via Makefile and build.sh ### Notes - Initial MVP release, all core features functional - Encrypted storage planned for future release - Interactive shell in Go SSH direct mode not yet available ## Phase 2 — Security Enhancement ### AES-256-GCM Encryption - New `pkg/crypto/crypto.go`: AES-256-GCM encrypt/decrypt with PBKDF2 key derivation - 100,000 iterations, 16-byte salt, SHA-256 key derivation - `Encrypt(plaintext, password)` → base64(salt + nonce + ciphertext) - `Decrypt(encoded, password)` → plaintext - `IsEncrypted(data)` checks if data looks like encrypted content ### Storage Layer Encryption - `JSONStorage` now has `password` field for master encryption key - `SetPassword()`, `GetPassword()`, `IsEncrypted()` methods - `readJSON()` auto-decrypts if password is set and data is encrypted - `replaceHosts/KeyPairs/Snippets()` auto-encrypt before writing - All existing CRUD operations transparently encrypt/decrypt ### Known Hosts Verification - New `pkg/knownhosts/knownhosts.go`: TOFU (Trust-On-First-Use) model - `KnownHosts` manages `known_hosts` file (JSON format) - `Verify()` checks if host key matches stored key - `HostKeyCallback()` returns `cryptossh.HostKeyCallback` for SSH config - Warns on key mismatch (potential MITM attack) ### SSH Client Security - `Client` now supports `hostKeyCallback` and `passphraseCallback` - `SetHostKeyCallback()` — replaces `InsecureIgnoreHostKey()` - `SetPassphraseCallback()` — prompts for passphrase on encrypted keys - `getKeySigner()` tries passphrase callback if key is encrypted ### Models Updated - `AppConfig`: added `EncryptionEnabled`, `PasswordHash`, `KnownHostsFile`