secret/internal/cli/cli.go
sneak 386a27c0b6 fix: resolve all revive linter issues
Added missing package comments:
- cmd/secret/main.go
- internal/cli/cli.go
- internal/secret/constants.go
- internal/vault/management.go
- pkg/bip85/bip85.go

Fixed comment format issues for exported items:
- EnvStateDir, EnvMnemonic, EnvUnlockPassphrase, EnvGPGKeyID in constants.go
- Metadata, UnlockerMetadata, SecretMetadata, Configuration in metadata.go
- AppBIP39, AppHDWIF, AppXPRV in bip85.go

Replaced unused parameters with underscore (_):
- generate.go:39 - parameter 'args'
- init.go:30 - parameter 'args'
- unlockers.go:39,77,102 - parameter 'args' or 'cmd'
- vault.go:37 - parameter 'args'
- management.go:34 - parameter 'target'
2025-07-15 06:06:48 +02:00

100 lines
2.6 KiB
Go

// Package cli implements the command-line interface for the secret application.
package cli
import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
"git.eeqj.de/sneak/secret/internal/secret"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"golang.org/x/term"
)
// Global scanner for consistent stdin reading
var stdinScanner *bufio.Scanner //nolint:gochecknoglobals // Needed for consistent stdin handling
// Instance encapsulates all CLI functionality and state
type Instance struct {
fs afero.Fs
stateDir string
cmd *cobra.Command
}
// NewCLIInstance creates a new CLI instance with the real filesystem
func NewCLIInstance() *Instance {
fs := afero.NewOsFs()
stateDir := secret.DetermineStateDir("")
return &Instance{
fs: fs,
stateDir: stateDir,
}
}
// NewCLIInstanceWithFs creates a new CLI instance with the given filesystem (for testing)
func NewCLIInstanceWithFs(fs afero.Fs) *Instance {
stateDir := secret.DetermineStateDir("")
return &Instance{
fs: fs,
stateDir: stateDir,
}
}
// NewCLIInstanceWithStateDir creates a new CLI instance with custom state directory (for testing)
func NewCLIInstanceWithStateDir(fs afero.Fs, stateDir string) *Instance {
return &Instance{
fs: fs,
stateDir: stateDir,
}
}
// SetFilesystem sets the filesystem for this CLI instance (for testing)
func (cli *Instance) SetFilesystem(fs afero.Fs) {
cli.fs = fs
}
// SetStateDir sets the state directory for this CLI instance (for testing)
func (cli *Instance) SetStateDir(stateDir string) {
cli.stateDir = stateDir
}
// GetStateDir returns the state directory for this CLI instance
func (cli *Instance) GetStateDir() string {
return cli.stateDir
}
// getStdinScanner returns a shared scanner for stdin to avoid buffering issues
func getStdinScanner() *bufio.Scanner {
if stdinScanner == nil {
stdinScanner = bufio.NewScanner(os.Stdin)
}
return stdinScanner
}
// readLineFromStdin reads a single line from stdin with a prompt
// Uses a shared scanner to avoid buffering issues between multiple calls
func readLineFromStdin(prompt string) (string, error) {
// Check if stderr is a terminal - if not, we can't prompt interactively
if !term.IsTerminal(syscall.Stderr) {
return "", fmt.Errorf("cannot prompt for input: stderr is not a terminal (running in non-interactive mode)")
}
fmt.Fprint(os.Stderr, prompt) // Write prompt to stderr, not stdout
scanner := getStdinScanner()
if !scanner.Scan() {
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("failed to read from stdin: %w", err)
}
return "", fmt.Errorf("failed to read from stdin: EOF")
}
return strings.TrimSpace(scanner.Text()), nil
}