9 Commits

Author SHA1 Message Date
ab5f430ee1 feat: add port forwarding history persistence 2025-09-12 11:29:17 +02:00
3da3a33530 feat: add direct host connection via sshm <host> with history tracking 2025-09-11 19:41:04 +02:00
12c1ab476c feat: centralize history storage in config directory
Automatically migrates existing ~/.ssh/sshm_history.json to platform-appropriate config location
2025-09-11 17:10:39 +02:00
e0e50ebfd0 fix(cmd): export variables for test accessibility
Export rootCmd->RootCmd and appVersion->AppVersion to fix test compilation errors. Update all references across cmd package and tests.
2025-09-10 11:13:15 +02:00
947afb2bbe build: strip 'v' prefix from version tag for binary
- Remove the 'v' prefix from the Git tag before injecting the version into the built binary
- Ensures the version string in the CLI does not include a leading 'v' (e.g. '1.2.3' instead of 'v1.2.3')
2025-09-10 10:40:22 +02:00
fe529792e3 Merge branch 'feature/add-move-command' into dev
Add move command feature:
- Allows moving a host from one config file to another when using includes
- Available both in the TUI interface and via CLI with 'sshm move'
2025-09-10 09:37:24 +02:00
5ee623d054 Merge branch 'feature/update-checker' into dev
Add update checker feature:
- Show update availability in the TUI interface
- Display update notification with sshm -v or --version
2025-09-10 09:34:01 +02:00
09423287fd feat: add move command to relocate SSH hosts between config files
- Add 'move' command with interactive file selector
- Implement atomic host moving between SSH config files
- Support for configs with include directives
- Add comprehensive error handling and validation
- Update help screen with improved two-column layout
2025-09-08 16:26:30 +02:00
4767267387 feat: add automatic version update checking and notifications
- Add internal/version module for GitHub release checking
- Integrate async version check in Bubble Tea UI
- Display update notification in main interface
- Add version check to --version/-v command output
- Include comprehensive version comparison and error handling
- Add unit tests for version parsing and comparison logic
2025-09-08 14:46:05 +02:00
12 changed files with 173 additions and 439 deletions

136
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,136 @@
name: Build Binaries
on:
push:
tags:
- '*'
release:
types: [created]
workflow_dispatch:
permissions:
contents: write
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
include:
# Linux AMD64
- goos: linux
goarch: amd64
suffix: linux-amd64
# Linux ARM64
- goos: linux
goarch: arm64
suffix: linux-arm64
# macOS AMD64 (Intel)
- goos: darwin
goarch: amd64
suffix: darwin-amd64
# macOS ARM64 (Apple Silicon)
- goos: darwin
goarch: arm64
suffix: darwin-arm64
# Windows AMD64
- goos: windows
goarch: amd64
suffix: windows-amd64
# Windows ARM64
- goos: windows
goarch: arm64
suffix: windows-arm64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: |
mkdir -p dist
VERSION=${GITHUB_REF#refs/tags/}
# Remove 'v' prefix if present for version injection
VERSION_CLEAN=${VERSION#v}
if [ "${{ matrix.goos }}" = "windows" ]; then
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.version=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }}.exe .
else
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.version=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }} .
fi
- name: Create archive
run: |
cd dist
if [ "${{ matrix.goos }}" = "windows" ]; then
zip sshm-${{ matrix.suffix }}.zip sshm-${{ matrix.suffix }}.exe
rm sshm-${{ matrix.suffix }}.exe
else
tar -czf sshm-${{ matrix.suffix }}.tar.gz sshm-${{ matrix.suffix }}
rm sshm-${{ matrix.suffix }}
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: sshm-${{ matrix.suffix }}
path: |
dist/sshm-${{ matrix.suffix }}.tar.gz
dist/sshm-${{ matrix.suffix }}.zip
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
merge-multiple: true
- name: Prepare release assets
run: |
mkdir -p release
find ./artifacts -name "*.tar.gz" -exec cp {} ./release/ \;
find ./artifacts -name "*.zip" -exec cp {} ./release/ \;
ls -la ./release/
- name: Check if pre-release
id: check_prerelease
run: |
if [[ "${GITHUB_REF#refs/tags/}" == *"-beta"* ]] || [[ "${GITHUB_REF#refs/tags/}" == *"-alpha"* ]] || [[ "${GITHUB_REF#refs/tags/}" == *"-rc"* ]] || [[ "${GITHUB_REF#refs/tags/}" == *"-dev"* ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: ./release/*
draft: false
prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
generate_release_notes: true
name: ${{ github.ref_name }}${{ steps.check_prerelease.outputs.is_prerelease == 'true' && ' (Pre-release)' || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,39 +0,0 @@
name: Release with GoReleaser
on:
push:
tags:
- '*'
permissions:
contents: write
# Required for Homebrew tap updates
issues: write
pull-requests: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Fetch full history for changelog generation
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Token for updating Homebrew tap (create this secret in your repo settings)
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}

View File

@@ -1,136 +0,0 @@
version: 2
project_name: sshm
before:
hooks:
- go mod tidy
- go test ./...
builds:
- id: sshm
main: ./main.go
binary: sshm
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
- "386"
- arm
ignore:
# Skip ARM for Windows (not commonly used)
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
env:
- CGO_ENABLED=0
ldflags:
- -s -w
- -X github.com/Gu1llaum-3/sshm/cmd.AppVersion={{.Version}}
flags:
- -trimpath
archives:
- id: sshm
formats: [ "tar.gz" ]
# Use zip for Windows
format_overrides:
- goos: windows
formats: [ "zip" ]
# Template for archive name
name_template: >-
{{ .ProjectName }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }}
files:
- LICENSE
- README.md
checksum:
name_template: "checksums.txt"
algorithm: sha256
changelog:
use: github
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
- "^ci:"
- "^chore:"
- "^build:"
groups:
- title: Features
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: Bug fixes
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Others
order: 999
# Homebrew tap configuration (Formula pour CLI)
brews:
- name: sshm
repository:
owner: Gu1llaum-3
name: homebrew-sshm
# Token with repo permissions for your homebrew-sshm repo
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
commit_author:
name: goreleaserbot
email: bot@goreleaser.com
commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}"
homepage: "https://github.com/Gu1llaum-3/sshm"
description: "A modern SSH connection manager for your terminal"
license: MIT
skip_upload: auto
# Test command to verify installation
test: |
system "#{bin}/sshm --version"
# Release configuration
release:
github:
owner: Gu1llaum-3
name: sshm
prerelease: auto
draft: false
replace_existing_draft: true
target_commitish: "{{ .Commit }}"
discussion_category_name: General
name_template: "{{.ProjectName}} {{.Version}}"
header: |
## SSHM {{.Version}}
Thank you for downloading SSHM!
### Installation
**Homebrew (macOS/Linux):**
```bash
brew tap Gu1llaum-3/sshm
brew install sshm
```
**Manual Installation:**
Download the appropriate binary for your platform from the assets below.
footer: |
## Full Changelog
See all changes at https://github.com/Gu1llaum-3/sshm/compare/{{.PreviousTag}}...{{.Tag}}
---
Released with ❤️ by [GoReleaser](https://github.com/goreleaser/goreleaser)
# Snapshot builds (for non-tag builds)
snapshot:
version_template: "{{ .Tag }}-snapshot-{{.ShortCommit}}"
# Metadata for package managers
metadata:
mod_timestamp: "{{ .CommitTimestamp }}"

View File

@@ -1,44 +0,0 @@
.PHONY: build build-local test clean release snapshot
# Version can be overridden via environment variable or command line
VERSION ?= dev
# Go build flags
LDFLAGS := -s -w -X github.com/Gu1llaum-3/sshm/cmd.AppVersion=$(VERSION)
# Build with specific version
build:
@mkdir -p dist
go build -ldflags="$(LDFLAGS)" -o dist/sshm .
# Build with git version
build-local: VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
build-local: build
# Run tests
test:
go test ./...
# Clean build artifacts
clean:
rm -rf dist
# Release with GoReleaser (requires tag)
release:
@if [ -z "$(shell git tag --points-at HEAD)" ]; then \
echo "Error: No git tag found at current commit. Create a tag first with: git tag vX.Y.Z"; \
exit 1; \
fi
goreleaser release --clean
# Build snapshot (without tag)
snapshot:
goreleaser release --snapshot --clean
# Check GoReleaser config
release-check:
goreleaser check
# Run GoReleaser in dry-run mode
release-dry-run:
goreleaser release --snapshot --skip=publish --clean

187
README.md
View File

@@ -25,29 +25,35 @@ SSHM is a beautiful command-line tool that transforms how you manage and connect
## ✨ Features ## ✨ Features
### 🚀 **Core Capabilities** ### 🎯 **Core Features**
- **🎨 Beautiful TUI Interface** - Navigate your SSH hosts with an elegant, interactive terminal UI - **🎨 Beautiful TUI Interface** - Navigate your SSH hosts with an elegant, interactive terminal UI
- **⚡ Quick Connect** - Connect to any host instantly through the TUI or the CLI with `sshm <host>` - **⚡ Quick Connect** - Connect to any host instantly
- **🔄 Port Forwarding** - Easy setup for Local, Remote, and Dynamic (SOCKS) forwarding with history persistence - **🔄 Port Forwarding** - Easy setup for Local, Remote, and Dynamic (SOCKS) forwarding
- **📝 Easy Management** - Add, edit, move, and manage SSH configurations seamlessly - **📝Easy Management** - Add, edit, and manage SSH configurations seamlessly
- **🏷️ Tag Support** - Organize your hosts with custom tags for better categorization - **🏷️ Tag Support** - Organize your hosts with custom tags for better categorization
- **🔍 Smart Search** - Find hosts quickly with built-in filtering and search - **🔍 Smart Search** - Find hosts quickly with built-in filtering and search
- **📝 Real-time Status** - Live SSH connectivity indicators with asynchronous ping checks and color-coded status
- **🔔 Smart Updates** - Automatic version checking with update notifications
- **📈 Connection History** - Track your SSH connections with last login timestamps
### 🛠️ **Technical Features**
- **🔒 Secure** - Works directly with your existing `~/.ssh/config` file - **🔒 Secure** - Works directly with your existing `~/.ssh/config` file
- **📁 Custom Config Support** - Use any SSH configuration file with the `-c` flag - **📁 Custom Config Support** - Use any SSH configuration file with the `-c` flag
- **📂 SSH Include Support** - Full support for SSH Include directives to organize configurations across multiple files - **📂 SSH Include Support** - Full support for SSH Include directives to organize configurations across multiple files
- **⚙️ SSH Options Support** - Add any SSH configuration option through intuitive forms - **⚙️ SSH Options Support** - Add any SSH configuration option through intuitive forms
- **🔄 Automatic Conversion** - Seamlessly converts between command-line and config formats - **🔄 Automatic Conversion** - Seamlessly converts between command-line and config formats
- **🔄 Automatic Backups** - Backup configurations automatically before changes
- **✅ Validation** - Prevent configuration errors with built-in validation ### 🛠️ **Management Operations**
- **🔗 ProxyJump Support** - Secure connection tunneling through bastion hosts - **Add new SSH hosts** with interactive forms
- **⌨️ Keyboard Shortcuts** - Power user navigation with vim-like shortcuts - **Edit existing configurations** in-place
- **🌐 Cross-platform** - Supports Linux, macOS (Intel & Apple Silicon), and Windows - **Delete hosts** with confirmation prompts
- **⚡ Lightweight** - Single binary with no dependencies, zero configuration required - **Port forwarding setup** with intuitive interface for Local (-L), Remote (-R), and Dynamic (-D) forwarding
- **Backup configurations** automatically before changes
- **Validate settings** to prevent configuration errors
- **ProxyJump support** for secure connection tunneling through bastion hosts
- **SSH Options management** - Add any SSH option with automatic format conversion
- **Full SSH compatibility** - Maintains compatibility with standard SSH tools
### 🎮 **User Experience**
- **Zero configuration** - Works out of the box with your existing SSH setup
- **Keyboard shortcuts** for power users
- **Cross-platform** - Supports Linux, macOS (Intel & Apple Silicon), and Windows
- **Lightweight** - Single binary with no dependencies
## 🚀 Quick Start ## 🚀 Quick Start
@@ -99,17 +105,10 @@ sshm
- `a` - Add new host - `a` - Add new host
- `e` - Edit selected host - `e` - Edit selected host
- `d` - Delete selected host - `d` - Delete selected host
- `m` - Move host to another config file (requires SSH Include directives)
- `f` - Port forwarding setup - `f` - Port forwarding setup
- `q` - Quit - `q` - Quit
- `/` - Search/filter hosts - `/` - Search/filter hosts
**Real-time Status Indicators:**
- 🟢 **Online** - Host is reachable via SSH
- 🟡 **Connecting** - Currently checking host connectivity
- 🔴 **Offline** - Host is unreachable or SSH connection failed
-**Unknown** - Connectivity status not yet determined
**Sorting & Filtering:** **Sorting & Filtering:**
- `s` - Switch between sorting modes (name ↔ last login) - `s` - Switch between sorting modes (name ↔ last login)
- `n` - Sort by **name** (alphabetical) - `n` - Sort by **name** (alphabetical)
@@ -159,7 +158,6 @@ SSHM provides an intuitive interface for setting up SSH port forwarding. Press `
- Configure ports and addresses with guided forms - Configure ports and addresses with guided forms
- Optional bind address configuration (defaults to 127.0.0.1) - Optional bind address configuration (defaults to 127.0.0.1)
- Real-time validation of port numbers and addresses - Real-time validation of port numbers and addresses
- **Port forwarding history** - Save frequently used configurations for quick reuse
- Connect automatically with configured forwarding options - Connect automatically with configured forwarding options
**Troubleshooting Port Forwarding:** **Troubleshooting Port Forwarding:**
@@ -220,15 +218,9 @@ SSHM provides both command-line operations and an interactive TUI interface:
# Launch interactive TUI mode for browsing and connecting to hosts # Launch interactive TUI mode for browsing and connecting to hosts
sshm sshm
# Connect directly to a specific host (with history tracking)
sshm my-server
# Launch TUI with custom SSH config file # Launch TUI with custom SSH config file
sshm -c /path/to/custom/ssh_config sshm -c /path/to/custom/ssh_config
# Connect directly with custom SSH config file
sshm my-server -c /path/to/custom/ssh_config
# Add a new host using interactive form # Add a new host using interactive form
sshm add sshm add
@@ -244,42 +236,13 @@ sshm edit my-server
# Edit host with custom SSH config file # Edit host with custom SSH config file
sshm edit my-server -c /path/to/custom/ssh_config sshm edit my-server -c /path/to/custom/ssh_config
# Move a host to another SSH config file (requires Include directives) # Show version information
sshm move my-server
# Move host with custom SSH config file (requires Include directives)
sshm move my-server -c /path/to/custom/ssh_config
# Search for hosts (interactive filter)
sshm search
# Show version information (includes update check)
sshm --version sshm --version
# Show help and available commands # Show help and available commands
sshm --help sshm --help
``` ```
### Direct Host Connection
SSHM supports direct connection to hosts via the command line, making it easy to integrate into your existing workflow:
```bash
# Connect directly to any configured host
sshm production-server
sshm db-staging
sshm web-01
# All direct connections are tracked in your history
# Use the TUI to see your most recently connected hosts
```
**Features of Direct Connection:**
- **Instant connection** - No TUI navigation required
- **History tracking** - All connections are recorded with timestamps
- **Error handling** - Clear messages if host doesn't exist or configuration issues
- **Config file support** - Works with custom config files using `-c` flag
### Backup Configuration ### Backup Configuration
SSHM automatically creates backups of your SSH configuration files before making any changes to ensure your configurations are safe. SSHM automatically creates backups of your SSH configuration files before making any changes to ensure your configurations are safe.
@@ -294,10 +257,6 @@ SSHM automatically creates backups of your SSH configuration files before making
- Stored separately to avoid SSH Include conflicts - Stored separately to avoid SSH Include conflicts
- Easy manual recovery if needed - Easy manual recovery if needed
**Additional Storage:**
- **Connection History**: Stored in the same config directory for persistent tracking
- **Port Forwarding History**: Saved configurations for quick reuse of common forwarding setups
**Quick Recovery:** **Quick Recovery:**
```bash ```bash
# Unix/Linux/macOS # Unix/Linux/macOS
@@ -318,96 +277,8 @@ sshm -c /path/to/custom/ssh_config
# Use custom config file with commands # Use custom config file with commands
sshm add hostname -c /path/to/custom/ssh_config sshm add hostname -c /path/to/custom/ssh_config
sshm edit hostname -c /path/to/custom/ssh_config sshm edit hostname -c /path/to/custom/ssh_config
sshm move hostname -c /path/to/custom/ssh_config
``` ```
### Advanced Features
#### Host Movement Between Config Files
SSHM provides a powerful `move` command to relocate SSH hosts between different configuration files. **This feature requires SSH Include directives to be present in your SSH configuration.**
```bash
# Move a host to another config file (requires Include directives)
sshm move my-server
# Move with custom config file (requires Include directives)
sshm move my-server -c /path/to/custom/ssh_config
```
**⚠️ Important Requirements:**
- **SSH Include directives must be present** in your SSH config file (either `~/.ssh/config` or the file specified with `-c`)
- The config file must contain `Include` statements referencing other SSH configuration files
- Without Include directives, the move command will display an error message
**Features:**
- **Interactive file selector** - Choose destination config file from Include directives
- **Include support** - Works seamlessly with SSH Include directives structure
- **Atomic operations** - Safe host movement with automatic backups
- **Validation** - Prevents conflicts and ensures configuration integrity
- **Error handling** - Clear messages when Include files are needed but not found
**Use Cases:**
- Reorganize hosts from main config to specialized include files
- Move development hosts to separate environment-specific configs
- Consolidate configurations for better organization
**Example Setup Required:**
Your main SSH config file must contain Include directives like:
```ssh
# ~/.ssh/config
Include ~/.ssh/config.d/*
Include work-servers.conf
Include projects/*.conf
Host personal-server
HostName personal.example.com
User myuser
```
#### Real-time Connectivity Status
SSHM features asynchronous SSH connectivity checking that provides visual indicators of host availability:
**Status Indicators:**
- 🟢 **Online** - SSH connection successful (shows response time)
- 🟡 **Connecting** - Currently testing connectivity
- 🔴 **Offline** - SSH connection failed or host unreachable
-**Unknown** - Status not yet determined
**Features:**
- **Non-blocking checks** - Status updates happen in the background
- **Response time tracking** - See connection latency for online hosts
- **Automatic refresh** - Status indicators update continuously
- **Error details** - Detailed error information for failed connections
#### Automatic Update Checking
SSHM includes built-in version checking that notifies you of available updates:
**Features:**
- **Background checking** - Version check happens asynchronously
- **Release notifications** - Clear indicators when updates are available
- **Pre-release detection** - Identifies beta and development versions
- **GitHub integration** - Direct links to release pages
- **Non-intrusive** - Updates don't interrupt your workflow
**Update notifications appear:**
- In the main TUI interface as a subtle notification
- In the `sshm --version` command output
- Only when a newer stable version is available
#### Port Forwarding History
SSHM remembers your port forwarding configurations for easy reuse:
**Features:**
- **Automatic saving** - Successful forwarding setups are saved automatically
- **Quick reuse** - Previously used configurations appear as suggestions
- **Per-host history** - Forwarding history is tracked per SSH host
- **All forward types** - Supports Local (-L), Remote (-R), and Dynamic (-D) forwarding history
- **Persistent storage** - History survives application restarts
### Platform-Specific Notes ### Platform-Specific Notes
**Windows:** **Windows:**
@@ -578,29 +449,20 @@ sshm/
│ ├── root.go # Root command and interactive mode │ ├── root.go # Root command and interactive mode
│ ├── add.go # Add host command │ ├── add.go # Add host command
│ ├── edit.go # Edit host command │ ├── edit.go # Edit host command
│ ├── move.go # Move host command
│ └── search.go # Search command │ └── search.go # Search command
├── internal/ ├── internal/
│ ├── config/ # SSH configuration management │ ├── config/ # SSH configuration management
│ │ └── ssh.go # Config parsing and manipulation │ │ └── ssh.go # Config parsing and manipulation
│ ├── connectivity/ # SSH connectivity checking
│ │ └── ping.go # Asynchronous SSH ping functionality
│ ├── history/ # Connection history tracking │ ├── history/ # Connection history tracking
│ │ ── history.go # History management and last login tracking │ │ ── history.go # History management and last login tracking
│ │ └── port_forward_test.go # Port forwarding history tests
│ ├── version/ # Version checking and updates
│ │ ├── version.go # GitHub release checking and version comparison
│ │ └── version_test.go # Version parsing and comparison tests
│ ├── ui/ # Terminal UI components (Bubble Tea) │ ├── ui/ # Terminal UI components (Bubble Tea)
│ │ ├── tui.go # Main TUI interface and program setup │ │ ├── tui.go # Main TUI interface and program setup
│ │ ├── model.go # Core TUI model and state │ │ ├── model.go # Core TUI model and state
│ │ ├── update.go # Message handling and state updates │ │ ├── update.go # Message handling and state updates
│ │ ├── view.go # UI rendering and layout │ │ ├── view.go # UI rendering and layout
│ │ ├── table.go # Host list table component with status indicators │ │ ├── table.go # Host list table component
│ │ ├── add_form.go # Add host form interface │ │ ├── add_form.go # Add host form interface
│ │ ├── edit_form.go# Edit host form interface │ │ ├── edit_form.go# Edit host form interface
│ │ ├── move_form.go# Move host form interface
│ │ ├── port_forward_form.go # Port forwarding setup with history
│ │ ├── styles.go # Lip Gloss styling definitions │ │ ├── styles.go # Lip Gloss styling definitions
│ │ ├── sort.go # Sorting and filtering logic │ │ ├── sort.go # Sorting and filtering logic
│ │ └── utils.go # UI utility functions │ │ └── utils.go # UI utility functions
@@ -628,7 +490,6 @@ sshm/
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework - [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
- [Bubbles](https://github.com/charmbracelet/bubbles) - TUI components - [Bubbles](https://github.com/charmbracelet/bubbles) - TUI components
- [Lipgloss](https://github.com/charmbracelet/lipgloss) - Styling - [Lipgloss](https://github.com/charmbracelet/lipgloss) - Styling
- [Go Crypto SSH](https://golang.org/x/crypto/ssh) - SSH connectivity checking
## 📦 Releases ## 📦 Releases

View File

@@ -4,10 +4,9 @@ import (
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"strings"
"github.com/Gu1llaum-3/sshm/internal/config" "github.com/Gu1llaum-3/sshm/internal/config"
"github.com/Gu1llaum-3/sshm/internal/validation" "github.com/Gu1llaum-3/sshm/internal/validation"
"strings"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@@ -149,8 +148,8 @@ func (m *addFormModel) Update(msg tea.Msg) (*addFormModel, tea.Cmd) {
case "ctrl+c", "esc": case "ctrl+c", "esc":
return m, func() tea.Msg { return addFormCancelMsg{} } return m, func() tea.Msg { return addFormCancelMsg{} }
case "ctrl+s": case "ctrl+enter":
// Allow submission from any field with Ctrl+S (Save) // Allow submission from any field with Ctrl+Enter
return m, m.submitForm() return m, m.submitForm()
case "tab", "shift+tab", "enter", "up", "down": case "tab", "shift+tab", "enter", "up", "down":
@@ -239,7 +238,7 @@ func (m *addFormModel) View() string {
b.WriteString("\n\n") b.WriteString("\n\n")
} }
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+S: save • Ctrl+C/Esc: cancel")) b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+Enter: submit • Ctrl+C/Esc: cancel"))
b.WriteString("\n") b.WriteString("\n")
b.WriteString(m.styles.FormHelp.Render("* Required fields")) b.WriteString(m.styles.FormHelp.Render("* Required fields"))

View File

@@ -1,10 +1,9 @@
package ui package ui
import ( import (
"strings"
"github.com/Gu1llaum-3/sshm/internal/config" "github.com/Gu1llaum-3/sshm/internal/config"
"github.com/Gu1llaum-3/sshm/internal/validation" "github.com/Gu1llaum-3/sshm/internal/validation"
"strings"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@@ -140,8 +139,8 @@ func (m *editFormModel) Update(msg tea.Msg) (*editFormModel, tea.Cmd) {
case "ctrl+c", "esc": case "ctrl+c", "esc":
return m, func() tea.Msg { return editFormCancelMsg{} } return m, func() tea.Msg { return editFormCancelMsg{} }
case "ctrl+s": case "ctrl+enter":
// Allow submission from any field with Ctrl+S (Save) // Allow submission from any field with Ctrl+Enter
return m, m.submitEditForm() return m, m.submitEditForm()
case "tab", "shift+tab", "enter", "up", "down": case "tab", "shift+tab", "enter", "up", "down":
@@ -248,7 +247,7 @@ func (m *editFormModel) View() string {
b.WriteString("\n\n") b.WriteString("\n\n")
} }
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+S: save • Ctrl+C/Esc: cancel")) b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+Enter: submit • Ctrl+C/Esc: cancel"))
b.WriteString("\n") b.WriteString("\n")
b.WriteString(m.styles.FormHelp.Render("* Required fields")) b.WriteString(m.styles.FormHelp.Render("* Required fields"))

View File

@@ -99,10 +99,6 @@ type Model struct {
height int height int
styles Styles styles Styles
ready bool ready bool
// Error handling
errorMessage string
showingError bool
} }
// updateTableStyles updates the table header border color based on focus state // updateTableStyles updates the table header border color based on focus state

View File

@@ -42,7 +42,7 @@ func NewMoveForm(hostName string, styles Styles, width, height int, configFile s
} }
if len(files) == 0 { if len(files) == 0 {
return nil, fmt.Errorf("no includes found in SSH config file - move operation requires multiple config files") return nil, fmt.Errorf("no other config files available to move host to")
} }
// Create a custom file selector for move operation // Create a custom file selector for move operation

View File

@@ -19,7 +19,6 @@ type (
pingResultMsg *connectivity.HostPingResult pingResultMsg *connectivity.HostPingResult
versionCheckMsg *version.UpdateInfo versionCheckMsg *version.UpdateInfo
versionErrorMsg error versionErrorMsg error
errorMsg string
) )
// startPingAllCmd creates a command to ping all hosts concurrently // startPingAllCmd creates a command to ping all hosts concurrently
@@ -158,14 +157,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// as it might disrupt the user experience // as it might disrupt the user experience
return m, nil return m, nil
case errorMsg:
// Handle general error messages
if string(msg) == "clear" {
m.showingError = false
m.errorMessage = ""
}
return m, nil
case addFormSubmitMsg: case addFormSubmitMsg:
if msg.err != nil { if msg.err != nil {
// Show error in form // Show error in form
@@ -596,13 +587,8 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
hostName := extractHostNameFromTableRow(selected[0]) // Extract hostname from first column hostName := extractHostNameFromTableRow(selected[0]) // Extract hostname from first column
moveForm, err := NewMoveForm(hostName, m.styles, m.width, m.height, m.configFile) moveForm, err := NewMoveForm(hostName, m.styles, m.width, m.height, m.configFile)
if err != nil { if err != nil {
// Show error message to user // Handle error - could show in UI, e.g., no other config files available
m.errorMessage = err.Error() return m, nil
m.showingError = true
return m, func() tea.Msg {
time.Sleep(3 * time.Second) // Show error for 3 seconds
return errorMsg("clear")
}
} }
m.moveForm = moveForm m.moveForm = moveForm
m.viewMode = ViewMove m.viewMode = ViewMove

View File

@@ -72,20 +72,6 @@ func (m Model) renderListView() string {
components = append(components, updateStyle.Render(updateText)) components = append(components, updateStyle.Render(updateText))
} }
// Add error message if there's one to show
if m.showingError && m.errorMessage != "" {
errorStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("9")). // Red color
Background(lipgloss.Color("1")). // Dark red background
Bold(true).
Padding(0, 1).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("9")).
Align(lipgloss.Center)
components = append(components, errorStyle.Render("❌ "+m.errorMessage))
}
// Add the search bar with the appropriate style based on focus // Add the search bar with the appropriate style based on focus
searchPrompt := "Search (/ to focus): " searchPrompt := "Search (/ to focus): "
if m.searchMode { if m.searchMode {

View File

@@ -128,8 +128,7 @@ func TestValidateIdentityFile(t *testing.T) {
{"empty path", "", true}, // Optional field {"empty path", "", true}, // Optional field
{"valid file", validFile, true}, {"valid file", validFile, true},
{"non-existent file", "/path/to/nonexistent", false}, {"non-existent file", "/path/to/nonexistent", false},
// Skip tilde path test in CI environments where ~/.ssh/id_rsa may not exist {"tilde path", "~/.ssh/id_rsa", true}, // Will pass if file exists
// {"tilde path", "~/.ssh/id_rsa", true}, // Will pass if file exists
} }
for _, tt := range tests { for _, tt := range tests {
@@ -139,15 +138,6 @@ func TestValidateIdentityFile(t *testing.T) {
} }
}) })
} }
// Test tilde path separately, but only if the file actually exists
t.Run("tilde path", func(t *testing.T) {
tildeFile := "~/.ssh/id_rsa"
// Just test that it doesn't crash, don't assume file exists
result := ValidateIdentityFile(tildeFile)
// Result can be true or false depending on file existence
_ = result // We just care that it doesn't panic
})
} }
func TestValidateHost(t *testing.T) { func TestValidateHost(t *testing.T) {
@@ -184,4 +174,4 @@ func TestValidateHost(t *testing.T) {
} }
}) })
} }
} }