Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ Since v2.1.3 it is possible to pipe the password from stdin and skip the input q
## Keys

```text
↑/k up
↓/j down
/ filter
enter show/hide token
u show/hide usernames
c copy
q quit
↑/k up
↓/j down
/ filter
enter toggle token visibility
u toggle usernames visibility
c/y yank token to system clipboard
q quit
```

## Clipboard config
Expand Down
22 changes: 11 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
module github.com/tjblackheart/andcli/v2

go 1.24
go 1.25

require (
github.com/ProtonMail/gopenpgp/v3 v3.3.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/bubbletea v1.3.8
github.com/charmbracelet/lipgloss v1.1.0
github.com/goccy/go-yaml v1.18.0
github.com/grijul/go-andotp v1.0.23
github.com/spf13/pflag v1.0.7
github.com/spf13/pflag v1.0.10
github.com/tobischo/gokeepasslib/v3 v3.6.1
github.com/xlzd/gotp v0.1.0
golang.org/x/crypto v0.40.0
golang.org/x/term v0.33.0
golang.org/x/crypto v0.42.0
golang.org/x/term v0.35.0
)

require (
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
Expand All @@ -37,7 +37,7 @@ require (
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/tobischo/argon2 v0.1.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/bubbletea v1.3.8 h1:DJlh6UUPhobzomqCtnLJRmhBSxwUJoPPi6iCToUDr4g=
github.com/charmbracelet/bubbletea v1.3.8/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
Expand All @@ -40,6 +46,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
Expand All @@ -61,6 +69,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tobischo/argon2 v0.1.0 h1:mwAx/9DK/4rP0xzNifb/XMAf43dU3eG1B3aeF88qu4Y=
Expand All @@ -74,24 +84,34 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3 h1:fJwx88sMf5RXwDwziL0/Mn9Wqs+efMSo/RYcL+37W9c=
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 changes: 5 additions & 18 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,16 @@ type (
// plus given flags into a current app config. Missing dirs apart
// from the default system config directory will be created in the process.
func Create() (*Config, error) {

userDir, err := os.UserConfigDir()
dir, err := os.UserConfigDir()
if err != nil {
return nil, fmt.Errorf("can not read directory: %s", err)
return nil, fmt.Errorf("unable to read user directory: %s", err)
}

return create(userDir)
return create(dir)
}

func create(dir string) (*Config, error) {
path, err := createDirectories(filepath.Join(dir, buildinfo.AppName))
if err != nil {
path := filepath.Join(dir, buildinfo.AppName)
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return nil, err
}

Expand Down Expand Up @@ -151,14 +149,3 @@ func (c *Config) validate() error {

return nil
}

func createDirectories(path string) (string, error) {

if _, err := os.Stat(path); os.IsNotExist(err) {
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return "", err
}
}

return path, nil
}
30 changes: 0 additions & 30 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,6 @@ import (
"github.com/tjblackheart/andcli/v2/internal/buildinfo"
)

func Test_createDirectories(t *testing.T) {
wantDir := filepath.Join(os.TempDir(), "andcli_test", "config", buildinfo.AppName)

tests := []struct {
name string
dir string
fails bool
}{
{"creates directories", wantDir, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createDirectories(tt.dir)
if (err != nil) != tt.fails {
t.Errorf("createDirectories() error = %v, wantErr %v", err, tt.fails)
return
}

if _, err := os.Stat(tt.dir); err != nil {
t.Errorf("createDirectories() = %v, want %v", got, tt.dir)
}

if err := os.RemoveAll(tt.dir); err != nil {
t.Error(err)
}
})
}
}

func TestConfig_mergeExisting(t *testing.T) {
path := filepath.Join(os.TempDir(), "andcli_test_config.yaml")

Expand Down
45 changes: 19 additions & 26 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,28 @@ type (
)

var (
style *defaultStyle
state *appState
cb *clipboard.Clipboard
style = newDefaultStyle()
state = new(appState)
cb = new(clipboard.Clipboard)
copyOK = ns().Foreground(green).Render("✓")
copyErr = ns().Foreground(red).Render("✕")
)

func New(entries []vaults.Entry, cfg *config.Config) Model {
state.showToken = cfg.Options.ShowTokens
state.showUsernames = cfg.Options.ShowUsernames

items := make([]list.Item, 0)
for _, e := range entries {
items = append(items, e)
}

cb = clipboard.New(cfg.ClipboardCmd)
state = &appState{
showToken: cfg.Options.ShowTokens,
showUsernames: cfg.Options.ShowUsernames,
}
style = newDefaultStyle()
title := fmt.Sprintf("%s: %s", buildinfo.AppName, filepath.Base(cfg.File))

keys := initKeys()
d := &itemDelegate{style}
list := initList(items, d, keys, title)
dlg := &itemDelegate{style}

return Model{list: list}
return Model{list: initList(items, dlg, keys, title)}
}

func (m Model) Init() tea.Cmd {
Expand All @@ -78,16 +76,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
state.showToken = !state.showToken
case "u":
state.showUsernames = !state.showUsernames
case "c":
case "c", "y":
if !cb.IsInitialized() {
break
msg := fmt.Sprintf("%s No clipboard command available", copyErr)
return m, m.list.NewStatusMessage(msg)
}

msg := "Token copied to clipboard"
msg := fmt.Sprintf("%s Token copied to clipboard", copyOK)
if err := cb.Set([]byte(state.currentOTP)); err != nil {
msg = fmt.Sprintf("%s: %s", cb.String(), err)
msg = fmt.Sprintf("%s %s: %s", copyErr, cb.String(), err)
}

return m, m.list.NewStatusMessage(msg)
}

Expand Down Expand Up @@ -115,16 +113,11 @@ func tick() tea.Cmd {
}

func initKeys() []key.Binding {
keys := []key.Binding{
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "show/hide token")),
key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "show/hide usernames")),
return []key.Binding{
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "toggle token")),
key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "toggle usernames")),
key.NewBinding(key.WithKeys("c", "y"), key.WithHelp("c/y", "yank to clipboard")),
}

if cb != nil && cb.IsInitialized() {
keys = append(keys, key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy")))
}

return keys
}

func initList(i []list.Item, d *itemDelegate, k []key.Binding, title string) list.Model {
Expand Down
48 changes: 0 additions & 48 deletions internal/model/model_test.go

This file was deleted.

21 changes: 7 additions & 14 deletions internal/model/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,19 @@ var ns = lipgloss.NewStyle

func newDefaultStyle() *defaultStyle {
return &defaultStyle{
title: ns().Background(base).Padding(0, 1),

listItem: ns().PaddingLeft(2).Faint(true),

title: ns().Background(base).Padding(0, 1),
listItem: ns().PaddingLeft(2).Faint(true),
username: ns().Background(grey),
filterPrompt: ns().Foreground(base),
filterCursor: ns().Background(base),
token: ns().Bold(true).Padding(0, 1, 0, 1),
until: ns().Bold(true),
activeItem: ns().
Padding(0, 1).
Bold(true).
Background(grey).
Border(lipgloss.ThickBorder(), false, false, false, true).
BorderForeground(base).
Faint(false),

username: ns().Background(grey),

filterPrompt: ns().Foreground(base),

filterCursor: ns().Background(base),

token: ns().Bold(true).Padding(0, 1, 0, 1),

until: ns().Bold(true),
}
}
Loading