summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod5
-rw-r--r--go.sum4
-rw-r--r--internal/shell/shell.go36
3 files changed, 23 insertions, 22 deletions
diff --git a/go.mod b/go.mod
index 392bffd..c979e3e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module codeberg.org/snonux/geheim
-go 1.24
+go 1.24.0
require (
github.com/ergochat/readline v0.1.3
@@ -8,6 +8,7 @@ require (
)
require (
- golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/sys v0.41.0 // indirect
+ golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.9.0 // indirect
)
diff --git a/go.sum b/go.sum
index 2d7624a..b8a6368 100644
--- a/go.sum
+++ b/go.sum
@@ -4,5 +4,9 @@ github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
+golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
diff --git a/internal/shell/shell.go b/internal/shell/shell.go
index 5395c97..1d231b9 100644
--- a/internal/shell/shell.go
+++ b/internal/shell/shell.go
@@ -6,10 +6,13 @@ package shell
import (
"context"
+ "fmt"
"io"
+ "os"
"strings"
"github.com/ergochat/readline"
+ "golang.org/x/term"
)
// Shell manages an interactive readline loop with vi mode and tab completion.
@@ -83,9 +86,9 @@ func New(completionFn func(prefix string) []string) (*Shell, error) {
// ReadLine reads one line from the terminal, applying history deduplication.
//
-// Behaviour mirrors the Ruby shell_loop:
+// Behaviour:
// - Ctrl+D (EOF) → returns ("", io.EOF) — caller should exit
-// - Ctrl+C (interrupt) → returns ("", nil) — empty line, continue
+// - Ctrl+C (interrupt) → returns ("", io.EOF) — caller should exit (same as Ruby's SIGINT)
// - non-empty line → saved to history only if it differs from the
// previous entry, then returned to the caller
//
@@ -99,8 +102,9 @@ func (s *Shell) ReadLine(ctx context.Context) (string, error) {
return "", io.EOF
}
if err == readline.ErrInterrupt {
- // Ctrl+C — return an empty line so the caller loops again.
- return "", nil
+ // Ctrl+C — exit the shell, mirroring the Ruby behaviour where
+ // SIGINT terminates the process.
+ return "", io.EOF
}
return "", err
}
@@ -137,25 +141,17 @@ func (s *Shell) ReadPassword(prompt string) (string, error) {
return string(bytes), nil
}
-// ReadPassword reads a password from stdin without echoing characters.
-// It is a package-level convenience function for use before the Shell is
-// created, such as during initial PIN entry at startup.
+// ReadPassword prints prompt then reads a password from the terminal without
+// echoing characters. It uses golang.org/x/term for reliable cross-platform
+// masked input, bypassing the readline library which does not always display
+// the prompt correctly before the process is fully interactive.
func ReadPassword(prompt string) (string, error) {
- // Create a minimal, temporary readline instance solely for masked input.
- // VimMode and history are irrelevant here; we just need the password-
- // reading capability.
- rl, err := readline.NewFromConfig(&readline.Config{
- Prompt: prompt,
- HistoryLimit: -1, // disable history for this throwaway instance
- })
- if err != nil {
- return "", err
- }
- defer rl.Close() //nolint:errcheck
+ fmt.Print(prompt)
+ defer fmt.Println() // move to next line after the user presses Enter
- bytes, err := rl.ReadPassword(prompt)
+ b, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
- return string(bytes), nil
+ return string(b), nil
}