From bc2cc73ac6f8dbe74a8178fd44c86f62701d0135 Mon Sep 17 00:00:00 2001 From: Gyuri Horak Date: Wed, 18 Dec 2024 04:42:32 +0100 Subject: [PATCH 1/7] Possibility to set clipboard command (#1) * possibility to set clipboard command * clipboard command added to README * copy failed message, copy related state moved into model --- README.md | 2 ++ cmd/andcli/config.go | 11 +++++-- cmd/andcli/main.go | 13 ++++---- cmd/andcli/tea.go | 75 ++++++++++++++++++++++++++------------------ 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index c50fa61..c4c6731 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ Usage of andcli: Path to the encrypted vault -t string Vault type (andotp, aegis) + -c string + Clipboard command (by default is the first of `xclip`, `wl-copy` or `pbcopy` found) -v Show current version ``` diff --git a/cmd/andcli/config.go b/cmd/andcli/config.go index 07ea4cf..1a2cd1b 100644 --- a/cmd/andcli/config.go +++ b/cmd/andcli/config.go @@ -10,11 +10,12 @@ import ( ) type config struct { - File string `yaml:"file"` - Type string `yaml:"type"` + File string `yaml:"file"` + Type string `yaml:"type"` + ClipboardCmd string `yaml:"clipboard_cmd"` } -func newConfig(vaultFile, vaultType string) (*config, error) { +func newConfig(vaultFile, vaultType, clipboardCmd string) (*config, error) { var err error if vaultFile != "" { @@ -62,6 +63,10 @@ func newConfig(vaultFile, vaultType string) (*config, error) { cfg.Type = vaultType } + if clipboardCmd != "" { + cfg.ClipboardCmd = clipboardCmd + } + if _, ok := os.LookupEnv("ANDCLI_HIDE_ABSPATH"); !ok { fmt.Printf("Using %s (%s)\n", cfg.File, cfg.Type) } diff --git a/cmd/andcli/main.go b/cmd/andcli/main.go index a66e5da..69cc3a6 100644 --- a/cmd/andcli/main.go +++ b/cmd/andcli/main.go @@ -34,10 +34,8 @@ var ( muted = color.New(color.FgHiWhite, color.Faint) // global ui stuff - copyCmd = "" - current = "" // holds an unformatted copy of the current token - copied = false - copiedVisibleMSecs = 2000 + copyCmd = "" + current = "" // holds an unformatted copy of the current token // build vars commit = "" @@ -61,11 +59,12 @@ func init() { } func main() { - var vaultFile, vaultType string + var vaultFile, vaultType, clipboardCmd string var showVersion bool flag.StringVar(&vaultFile, "f", "", "Path to the encrypted vault") flag.StringVar(&vaultType, "t", "", "Vault type (andotp, aegis)") + flag.StringVar(&clipboardCmd, "c", "", "Clipboard command (xclip, wl-copy, pbcopy, etc.)") flag.BoolVar(&showVersion, "v", false, "Show current version") flag.Parse() @@ -83,7 +82,7 @@ func main() { prefix := danger.Sprint("[ERR]") - cfg, err := newConfig(vaultFile, vaultType) + cfg, err := newConfig(vaultFile, vaultType, clipboardCmd) if err != nil { log.Fatalf("%s: %s\n", prefix, err.Error()) } @@ -108,7 +107,7 @@ func main() { output := termenv.DefaultOutput() output.ClearScreen() - p := tea.NewProgram(newModel(output, cfg.File, entries...)) + p := tea.NewProgram(newModel(output, cfg.File, cfg.ClipboardCmd, entries...)) if _, err := p.Run(); err != nil { log.Fatalf("%s: %s\n", prefix, err.Error()) } diff --git a/cmd/andcli/tea.go b/cmd/andcli/tea.go index 946fa24..82ee0b4 100644 --- a/cmd/andcli/tea.go +++ b/cmd/andcli/tea.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "log" "os/exec" "path/filepath" "strings" @@ -14,34 +13,44 @@ import ( type ( model struct { - filename string - items entries - filtered entries - cursor int - selected int - view string - visible bool - query string - output *termenv.Output + filename string + items entries + filtered entries + cursor int + selected int + view string + visible bool + query string + output *termenv.Output + copied bool + copyFailed bool + copiedVisibleMSecs int } tickMsg struct{} ) -func newModel(o *termenv.Output, filename string, entries ...entry) *model { +func newModel(o *termenv.Output, filename, cfgClipboardCmd string, entries ...entry) *model { m := &model{ - filename: filename, - items: entries, - selected: -1, - view: VIEW_LIST, - output: o, + filename: filename, + items: entries, + selected: -1, + view: VIEW_LIST, + output: o, + copied: false, + copyFailed: false, + copiedVisibleMSecs: 2000, } - cmds := []string{"xclip", "wl-copy", "pbcopy"} // xorg, wayland, macos - for _, c := range cmds { - if _, err := exec.LookPath(c); err == nil { - copyCmd = c - break + if cfgClipboardCmd != "" { + copyCmd = cfgClipboardCmd + } else { + cmds := []string{"xclip", "wl-copy", "pbcopy"} // xorg, wayland, macos + for _, c := range cmds { + if _, err := exec.LookPath(c); err == nil { + copyCmd = c + break + } } } @@ -167,20 +176,22 @@ func (m *model) updateDetail(msg tea.Msg) (tea.Model, tea.Cmd) { if current != "" && copyCmd != "" { cmd := fmt.Sprintf("echo %s | %s", current, copyCmd) if err := exec.Command("sh", "-c", cmd).Run(); err != nil { - log.Println("copy:", err) - return m, tea.Quit + m.copyFailed = true + m.copied = false + return m, nil } - copied = true + m.copyFailed = false + m.copied = true } } } case tickMsg: - if copied { - if copiedVisibleMSecs > 0 { - copiedVisibleMSecs-- + if m.copied { + if m.copiedVisibleMSecs > 0 { + m.copiedVisibleMSecs-- } else { - copied = false - copiedVisibleMSecs = 2000 + m.copied = false + m.copiedVisibleMSecs = 2000 } } @@ -233,10 +244,14 @@ func (m *model) detail() string { fmtUntil = danger.Sprintf("%ds", until) } - if copied { + if m.copied { fmtToken += success.Sprint(" ✓ ") } + if m.copyFailed { + fmtToken += danger.Sprint(" ✗ \nCopy command (" + copyCmd + ") failed, check your configuration!") + } + view := fmt.Sprintf("%s: %s\nValid: %s\n", name, fmtToken, fmtUntil) return view + m.footer() From fd70aafae99dcd530199885cc64fbc6fd6f49a15 Mon Sep 17 00:00:00 2001 From: Thomas Gensicke Date: Wed, 18 Dec 2024 10:55:51 +0700 Subject: [PATCH 2/7] fix tests --- cmd/andcli/andcli_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/andcli/andcli_test.go b/cmd/andcli/andcli_test.go index 08fee54..7a40a77 100644 --- a/cmd/andcli/andcli_test.go +++ b/cmd/andcli/andcli_test.go @@ -188,7 +188,7 @@ func TestConfig(t *testing.T) { cfgDir = os.TempDir() cfgFile = filepath.Join(cfgDir, "config_test.yaml") - cfg, err := newConfig(filepath.Join(tt.vaultDir, tt.vaultFile), tt.vaultType) + cfg, err := newConfig(filepath.Join(tt.vaultDir, tt.vaultFile), tt.vaultType, "") if tt.fails { assert.Error(t, err) return @@ -234,7 +234,7 @@ func TestChoices(t *testing.T) { o := termenv.DefaultOutput() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - m := newModel(o, "", tt.entries...) + m := newModel(o, "", "", tt.entries...) assert.Equal(t, tt.want.items, m.items) }) } From ade6d15d0a0fb2825fa766e4bbc55594b438b8e8 Mon Sep 17 00:00:00 2001 From: Wu Tingfeng Date: Wed, 18 Dec 2024 15:19:48 +0800 Subject: [PATCH 3/7] Add support for 2FAS (#2) * Add support for 2FAS * Ensure SplitN checks for excessive fields. Move magic numbers to const. * Revert go.sum changes --- README.md | 5 +- cmd/andcli/andcli_test.go | 88 ++++++++++ cmd/andcli/main.go | 5 +- cmd/andcli/testdata/twofas-export-test.2fas | 1 + cmd/andcli/testdata/twofas-invalid-file.2fas | 1 + cmd/andcli/twofas.go | 171 +++++++++++++++++++ 6 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 cmd/andcli/testdata/twofas-export-test.2fas create mode 100644 cmd/andcli/testdata/twofas-invalid-file.2fas create mode 100644 cmd/andcli/twofas.go diff --git a/README.md b/README.md index c4c6731..34cc61c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ View, decrypt and copy 2FA tokens from encrypted backup files directly in your s * [andotp](https://github.com/andOTP/andOTP) * [aegis](https://getaegis.app) +* [twofas](https://2fas.com) ![Demo](doc/demo.gif "Demo") @@ -16,7 +17,7 @@ Download a [prebuild release](https://github.com/tjblackheart/andcli/releases) a ## Usage 1. Export an **encrypted, password protected** backup from your 2FA app and save it into your preferred cloud provider (i.e. Dropbox, Nextcloud...). -2. Fire up `andcli` and point it to this file with `-f `. Specify the vault type via `-t `: choose between `andotp` or `aegis`. The path and type will get cached, so you have to do this only once. +2. Fire up `andcli` and point it to this file with `-f `. Specify the vault type via `-t `: choose between `andotp` or `aegis` or `twofas`. The path and type will get cached, so you have to do this only once. 3. Enter the encryption password. 4. To search an entry, type a word. Press `ESC` to clear the current query. 5. Navigate via keyboard, press `Enter` to view a token and press `c` to copy it into the clipboard (**Linux/Mac only**). @@ -35,7 +36,7 @@ Usage of andcli: -f string Path to the encrypted vault -t string - Vault type (andotp, aegis) + Vault type (andotp, aegis, twofas) -c string Clipboard command (by default is the first of `xclip`, `wl-copy` or `pbcopy` found) -v Show current version diff --git a/cmd/andcli/andcli_test.go b/cmd/andcli/andcli_test.go index 7a40a77..8414cec 100644 --- a/cmd/andcli/andcli_test.go +++ b/cmd/andcli/andcli_test.go @@ -37,6 +37,36 @@ func TestDecrypt(t *testing.T) { } } +func TestTwoFas(t *testing.T) { + tests := []struct { + name string + filename string + password string + fails bool + }{ + {"decrypts", "testdata/twofas-export-test.2fas", "andcli-test", false}, + {"fails: wrong password", "testdata/twofas-export-test.2fas", "invalid", true}, + {"fails: invalid file", "testdata/twofas-invalid-file.2fas", "invalid", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := os.ReadFile(tt.filename) + assert.NoError(t, err) + + entries, err := decryptTWOFAS(b, []byte(tt.password)) + if tt.fails { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Len(t, entries, 1) + assert.Equal(t, entries[0].Label, "andcli-test") + }) + } +} + func TestAEGIS(t *testing.T) { tests := []struct { name string @@ -159,6 +189,64 @@ func TestConvertAEGISEntry(t *testing.T) { assert.Equal(t, have.toEntry(), want) } +func TestConvertTwoFasEntry(t *testing.T) { + have := twofasEntry{ + Name: "name", + Secret: "secret", + UpdatedAt: 1707198293593, + Otp: struct { + Label string + Account string + Issuer string + Digits int + Period int + Algorithm string + TokenType string `json:"tokenType"` + Source string + }{ + Label: "name", + Account: "andcli-test", + Issuer: "issuer", + Digits: 6, + Period: 30, + Algorithm: "algo", + TokenType: "type", + Source: "Link", + }, + Order: struct { + Position int + }{Position: 0}, + Icon: struct { + Selected string + Label struct { + Text string + BackgroundColor string `json:"backgroundColor"` + } + IconCollection struct { + Id string + } `json:"iconCollection"` + }{ + Selected: "Label", Label: struct { + Text string + BackgroundColor string `json:"backgroundColor"` + }{Text: "OT", BackgroundColor: "Orange"}, IconCollection: struct { + Id string + }{Id: "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad"}}, + } + + want := &entry{ + Secret: "secret", + Issuer: "issuer", + Label: "name", + Digits: 6, + Type: "type", + Algorithm: "algo", + Period: 30, + } + + assert.Equal(t, have.toEntry(), want) +} + func TestConfig(t *testing.T) { cwd, err := os.Getwd() assert.NoError(t, err) diff --git a/cmd/andcli/main.go b/cmd/andcli/main.go index 69cc3a6..6bad68f 100644 --- a/cmd/andcli/main.go +++ b/cmd/andcli/main.go @@ -19,6 +19,7 @@ const ( VIEW_DETAIL = "detail" TYPE_ANDOTP = "andotp" TYPE_AEGIS = "aegis" + TYPE_TWOFAS = "twofas" ) var ( @@ -63,7 +64,7 @@ func main() { var showVersion bool flag.StringVar(&vaultFile, "f", "", "Path to the encrypted vault") - flag.StringVar(&vaultType, "t", "", "Vault type (andotp, aegis)") + flag.StringVar(&vaultType, "t", "", "Vault type (andotp, aegis, twofas)") flag.StringVar(&clipboardCmd, "c", "", "Clipboard command (xclip, wl-copy, pbcopy, etc.)") flag.BoolVar(&showVersion, "v", false, "Show current version") flag.Parse() @@ -148,6 +149,8 @@ func decrypt(vaultFile, vaultType string, p ...[]byte) (entries, error) { return decryptANDOTP(b, pass) case TYPE_AEGIS: return decryptAEGIS(b, pass) + case TYPE_TWOFAS: + return decryptTWOFAS(b, pass) } return nil, fmt.Errorf("vault type %q: not implemented", vaultType) diff --git a/cmd/andcli/testdata/twofas-export-test.2fas b/cmd/andcli/testdata/twofas-export-test.2fas new file mode 100644 index 0000000..b513d0f --- /dev/null +++ b/cmd/andcli/testdata/twofas-export-test.2fas @@ -0,0 +1 @@ +{"services":[],"groups":[],"updatedAt":1707198500794,"schemaVersion":4,"appVersionCode":5000012,"appVersionName":"5.2.0","appOrigin":"android","servicesEncrypted":"ejHkcIU7/KtjZkdm0mx+X+BY/jWAO1lbZaLpiSfADFsEFOo+rUhbaZ+i0a/0DKg5fBooJBqD4h8kJ20lHvkv2gCU30cy/hEs6vBZWTPBwr8dfC07NStxgWY3K78NBi5rhkXfe7QdHxMzhIXsnGOBqp1ibL5INCPESw9BlXQ1OWox/MNbIl9wRg0gRSyag+AF5xWdZ/AqpT/Gx4WM9bMw9MbM7zNKLGEHrlqf3ESO5r2JvPpB/Y2XiM87nCRrpCfQYsWLUwfKG+KkvokuXwwfH9lh8H0MZWqzIYHO8EW1rrBeW6arbjnInAjI4u71n0/MIRyiA+t6RVaGlqMXztAR4yo4Ts1e2RwBWA2TGOWMPoXXzj+uxjSDHmRv1/zDvkcg9FiP0xH2Ftr/fZYYwtUcsX4X5L6Jg+nvLR0wN6Al/zl3yHjgLn0vPMO9YMqtWFo2mnLuBHa8Epaey6ZCLQ6HkT4YHj3H7wQcRrc06Wy+vYs7nNHO5lD9wY+lpdxJGMeXv7nYSkTmSQwfIBSeuhngDZfMqRPZKqF74pjmb2Z/6pIP2ZdiKgceswN7an+ZXVxvDeS3jiS//cQyA9jJrkJB3tk=:w3uke3TsIcM0v0IWyIFNZTZTBkPVoKvckvikTEq/CeKPlUgmICJdhlSdMUuI4m9UO3jUqoAv5uCeLhn0XN5JycEklpd2p9rJUc5aSj3uhDb+Ki1oWBG6K5ePX82NMxp/xyWFZMWQrUbkXxSOXckV06F2ohrpz6hp2DpVgc+WkLecLQ4h2PzO9wEPrEKl+5B/iNnzI+2WXU8CW9oSg4B3JyFI/UVEk/80jMFs2lYFMAV7EmxbOBMuQrf2H/bzBPwYUCAbtqN9nfx54ywXfaHQHX/p11HbmdWgyp2PNAxFi2VqoY/c1TG0OSqs2RVbwYMtAMIGhnQWIcvtMJp4FV1/2A==:Ow6fbkwu/65x56U3","reference":""} diff --git a/cmd/andcli/testdata/twofas-invalid-file.2fas b/cmd/andcli/testdata/twofas-invalid-file.2fas new file mode 100644 index 0000000..50a269a --- /dev/null +++ b/cmd/andcli/testdata/twofas-invalid-file.2fas @@ -0,0 +1 @@ +"invalid" diff --git a/cmd/andcli/twofas.go b/cmd/andcli/twofas.go new file mode 100644 index 0000000..d595218 --- /dev/null +++ b/cmd/andcli/twofas.go @@ -0,0 +1,171 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + + "golang.org/x/crypto/pbkdf2" +) + +const numFields int = 3 +const authTagLength int = 16 + +type ( + twofasVault struct { + UpdatedAt int + SchemaVersion int + AppVersionCode int + AppVersionName string + AppOrigin string + ServicesEncrypted string + } + + twofasDB []twofasEntry + + twofasEntry struct { + Name string + Secret string + UpdatedAt int + Otp struct { + Label string + Account string + Issuer string + Digits int + Period int + Algorithm string + TokenType string `json:"tokenType"` + Source string + } + Order struct { + Position int + } + Icon struct { + Selected string + Label struct { + Text string + BackgroundColor string `json:"backgroundColor"` + } + IconCollection struct { + Id string + } `json:"iconCollection"` + } + } +) + +func (e twofasEntry) toEntry() *entry { + return &entry{ + Secret: e.Secret, + Issuer: e.Otp.Issuer, + Label: e.Otp.Label, + Digits: e.Otp.Digits, + Type: e.Otp.TokenType, + Algorithm: e.Otp.Algorithm, + Period: e.Otp.Period, + } +} + +// + +func decryptTWOFAS(data, password []byte) (entries, error) { + + var vault twofasVault + if err := json.Unmarshal(data, &vault); err != nil { + return nil, err + } + + key, err := deriveTwoFasMasterKey(&vault, password) + if err != nil { + return nil, err + } + + plain, err := decryptTwoFasDB(&vault, key) + if err != nil { + return nil, err + } + + var db twofasDB + if err := json.Unmarshal(plain, &db); err != nil { + return nil, err + } + + var list entries + for _, e := range db { + list = append(list, *e.toEntry()) + } + + return list, nil +} + +func deriveTwoFasMasterKey(v *twofasVault, password []byte) ([]byte, error) { + servicesEncrypted := strings.SplitN(v.ServicesEncrypted, ":", numFields+1) + if len(servicesEncrypted) != numFields { + return nil, fmt.Errorf("Invalid vault file. Number of fields is not %d", numFields) + } + var dbAndAuthTag, salt []byte + var err error + + dbAndAuthTag, err = base64.StdEncoding.DecodeString(servicesEncrypted[0]) + if err != nil { + return nil, err + } + salt, err = base64.StdEncoding.DecodeString(servicesEncrypted[1]) + if err != nil { + return nil, err + } + + if len(dbAndAuthTag) <= authTagLength { + return nil, fmt.Errorf("Invalid vault file. Length of cipher text with auth tag must be more than %d", authTagLength) + } + + return pbkdf2.Key(password, salt, 10000, 32, sha256.New), nil +} + +func decryptTwoFasDB(v *twofasVault, key []byte) ([]byte, error) { + + servicesEncrypted := strings.SplitN(v.ServicesEncrypted, ":", numFields+1) + if len(servicesEncrypted) != numFields { + return nil, fmt.Errorf("Invalid vault file. Number of fields is not %d", numFields) + } + + var dbAndAuthTag, b, tag, nonce []byte + var err error + + dbAndAuthTag, err = base64.StdEncoding.DecodeString(servicesEncrypted[0]) + if err != nil { + return nil, err + } + + b = dbAndAuthTag[:len(dbAndAuthTag)-authTagLength] + tag = dbAndAuthTag[len(dbAndAuthTag)-authTagLength:] + + nonce, err = base64.StdEncoding.DecodeString(servicesEncrypted[2]) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + var c []byte + c = append(c, b...) + c = append(c, tag...) + + plain, err := gcm.Open(nil, nonce, c, nil) + if err != nil { + return nil, err + } + + return plain, nil +} From b929871a854fd8bfc67721f47fdbacc252d45aa5 Mon Sep 17 00:00:00 2001 From: Wu Tingfeng Date: Sun, 29 Dec 2024 23:46:41 +0800 Subject: [PATCH 4/7] Fix xclip not working on xorg and add Windows support (#3) * Fix xclip not working on xorg * Use cross-platform clipboard package. * Use existing copy methods for macOS/Linux. Fallback to clipboard package for Windows. * Upgrade GitHub Actions * Update README * Fix PowerShell compatibility * Add missing import --- .github/workflows/build.yaml | 9 ++- .github/workflows/test.yaml | 13 +++-- Makefile | 8 +-- README.md | 6 +- cmd/andcli/tea.go | 26 +++++++-- go.mod | 30 +++++----- go.sum | 105 ++++++++++++----------------------- 7 files changed, 91 insertions(+), 106 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 33a12ab..eaf63a6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -8,10 +8,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.22" check-latest: true cache: true @@ -20,8 +20,7 @@ jobs: - name: Test run: | - go test -v -coverprofile=/tmp/codecov.out ./... && \ - go tool cover -func=/tmp/codecov.out + go test -v -coverprofile codecov.out ./... && go tool cover -func codecov.out - name: Get tag id: get_tag diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5f3caeb..a5e486e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,15 +9,18 @@ on: jobs: test: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: "1.19" check-latest: true cache: true - name: test run: | - go test -v -coverprofile=/tmp/codecov.out ./... && \ - go tool cover -func=/tmp/codecov.out + go test -v -coverprofile codecov.out ./... && go tool cover -func codecov.out diff --git a/Makefile b/Makefile index a393698..132fc6a 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,17 @@ GOVER=$(shell go version | sed 's/^.*go\([0-9.]*\).*/\1/') COMMIT=$(shell git rev-parse --short HEAD) -NOW=$(shell date --rfc-3339=seconds) +NOW=$(shell date --rfc-3339 seconds) FLAGS=-s -w -X 'main.commit=$(COMMIT)' -X 'main.gover=$(GOVER)' -X 'main.date=$(NOW)' # set local vars without pipeline access -TAG=$(shell git describe --tags --abbrev=0) +TAG=$(shell git describe --tags --abbrev 0) ARCH=$(shell go env GOARCH) build: clean - go build -ldflags="$(FLAGS) -X 'main.tag=$(TAG)' -X 'main.arch=$(ARCH)'" -o bin/andcli ./... + go build -ldflags "$(FLAGS) -X 'main.tag=$(TAG)' -X 'main.arch=$(ARCH)'" -o bin/andcli ./... ci: - go build -ldflags="$(FLAGS) -X 'main.tag=$(CI_TAG)' -X 'main.arch=$(GOARCH)'" -o bin/andcli_$(RELEASE) ./... + go build -ldflags "$(FLAGS) -X 'main.tag=$(CI_TAG)' -X 'main.arch=$(GOARCH)'" -o bin/andcli_$(RELEASE) ./... compress: build upx bin/andcli diff --git a/README.md b/README.md index 34cc61c..322ad3b 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,12 @@ Download a [prebuild release](https://github.com/tjblackheart/andcli/releases) a 2. Fire up `andcli` and point it to this file with `-f `. Specify the vault type via `-t `: choose between `andotp` or `aegis` or `twofas`. The path and type will get cached, so you have to do this only once. 3. Enter the encryption password. 4. To search an entry, type a word. Press `ESC` to clear the current query. -5. Navigate via keyboard, press `Enter` to view a token and press `c` to copy it into the clipboard (**Linux/Mac only**). -6. If you are running Linux: Press the middle mouse button to paste the token. On Mac, hit CMD+v. +5. Navigate via keyboard, press `Enter` to view a token and press `c` to copy it into the clipboard. +6. If you are running Linux: Press the middle mouse button to paste the token. On Mac, hit CMD+v. On Windows, hit Ctrl+v. ## TODO -* At the moment it is not possible to copy a token on a Windows machine. +* ~~At the moment it is not possible to copy a token on a Windows machine.~~ * The test coverage sucks (less). * ~~Implement a search.~~ diff --git a/cmd/andcli/tea.go b/cmd/andcli/tea.go index 82ee0b4..52b85d9 100644 --- a/cmd/andcli/tea.go +++ b/cmd/andcli/tea.go @@ -1,7 +1,9 @@ package main import ( + "bytes" "fmt" + "log" "os/exec" "path/filepath" "strings" @@ -9,6 +11,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/muesli/termenv" + "golang.design/x/clipboard" ) type ( @@ -53,6 +56,11 @@ func newModel(o *termenv.Output, filename, cfgClipboardCmd string, entries ...en } } } + if copyCmd == "" { // windows + if err := clipboard.Init(); err == nil { + copyCmd = "clipboard" + } + } for i, e := range m.items { issuer := strings.TrimSpace(e.Issuer) @@ -174,11 +182,19 @@ func (m *model) updateDetail(msg tea.Msg) (tea.Model, tea.Cmd) { } if msg.String() == "c" { if current != "" && copyCmd != "" { - cmd := fmt.Sprintf("echo %s | %s", current, copyCmd) - if err := exec.Command("sh", "-c", cmd).Run(); err != nil { - m.copyFailed = true - m.copied = false - return m, nil + if copyCmd == "clipboard" { + currentBytes := []byte(current) + clipboard.Write(clipboard.FmtText, currentBytes) + if !bytes.Equal(clipboard.Read(clipboard.FmtText), currentBytes) { + log.Println("copy: failed") + return m, tea.Quit + } + } else { + cmd := fmt.Sprintf("echo %s | %s -selection clipboard", current, copyCmd) + if err := exec.Command("sh", "-c", cmd).Run(); err != nil { + log.Println("copy:", err) + return m, tea.Quit + } } m.copyFailed = false m.copied = true diff --git a/go.mod b/go.mod index 521d595..7416440 100644 --- a/go.mod +++ b/go.mod @@ -3,32 +3,36 @@ module github.com/tjblackheart/andcli go 1.19 require ( - github.com/charmbracelet/bubbletea v0.24.2 - github.com/fatih/color v1.15.0 + github.com/charmbracelet/bubbletea v0.25.0 + github.com/fatih/color v1.16.0 github.com/grijul/go-andotp v1.0.23 - github.com/muesli/termenv v0.15.1 - github.com/stretchr/testify v1.8.3 + github.com/muesli/termenv v0.15.2 + github.com/stretchr/testify v1.8.4 github.com/xlzd/gotp v0.1.0 - golang.org/x/crypto v0.10.0 - golang.org/x/term v0.9.0 + golang.design/x/clipboard v0.7.0 + golang.org/x/crypto v0.18.0 + golang.org/x/term v0.16.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/containerd/console v1.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // 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.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + github.com/rivo/uniseg v0.4.6 // indirect + golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3 // indirect + golang.org/x/image v0.15.0 // indirect + golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 2aa6473..4bf8976 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,13 @@ -github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps= -github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM= -github.com/charmbracelet/bubbletea v0.24.1 h1:LpdYfnu+Qc6XtvMz6d/6rRY71yttHTP5HtrjMgWvixc= -github.com/charmbracelet/bubbletea v0.24.1/go.mod h1:rK3g/2+T8vOSEkNHvtq40umJpeVYDn6bLaqbgzhL/hg= -github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= -github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= +github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/grijul/go-andotp v1.0.23 h1:VOmfz0JqMsed0Y2RwVZ3hWji/5mVamWSKo2jrhDKQIE= github.com/grijul/go-andotp v1.0.23/go.mod h1:p/P8EpDp1qYf5JmSslmqlEbyNKtUZ98J3prJm5jZeUk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -23,90 +15,61 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= -github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= -github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= +github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= +golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo= +golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3 h1:tImqKNm/Iclm3Rqb6GffLiURSp3m1iRx/C4mturH8Ys= +golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b h1:kfWLZgb8iUBHdE9WydD5V5dHIS/F6HjlBZNyJfn2bs4= +golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b/go.mod h1:4efzQnuA1nICq6h4kmZRMGzbPiP06lZvgADUu1VpJCE= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 05ea8fa1c9817adaba0650156776562581e98d1b Mon Sep 17 00:00:00 2001 From: Wu Tingfeng Date: Sun, 29 Dec 2024 23:59:39 +0800 Subject: [PATCH 5/7] Change LastUsed type to int64 (#7) --- cmd/andcli/andotp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/andcli/andotp.go b/cmd/andcli/andotp.go index 564f282..6970d09 100644 --- a/cmd/andcli/andotp.go +++ b/cmd/andcli/andotp.go @@ -15,8 +15,8 @@ type andotpEntry struct { Algorithm string Thumbnail string Period int - LastUsed int `json:"last_used"` - UsedFreq int `json:"used_frequency"` + LastUsed int64 `json:"last_used"` + UsedFreq int `json:"used_frequency"` Tags []string } From 97b1383f2bf58527289c61f8309135ecf03737bb Mon Sep 17 00:00:00 2001 From: Wu Tingfeng Date: Mon, 30 Dec 2024 00:10:44 +0800 Subject: [PATCH 6/7] Replace `date --rfc-3339=seconds` with macOS/BSD compatible equivalent (#8) Co-authored-by: tjblackheart --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 132fc6a..d876e3b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ GOVER=$(shell go version | sed 's/^.*go\([0-9.]*\).*/\1/') COMMIT=$(shell git rev-parse --short HEAD) -NOW=$(shell date --rfc-3339 seconds) +# macOS/BSD compatible equivalent of `date --rfc-3339=seconds` +NOW=$(shell date "+%F %T%:z") FLAGS=-s -w -X 'main.commit=$(COMMIT)' -X 'main.gover=$(GOVER)' -X 'main.date=$(NOW)' # set local vars without pipeline access From 4cbcdcddff184c138bff98cc2e1fb7c17040b2c7 Mon Sep 17 00:00:00 2001 From: Wu Tingfeng Date: Thu, 9 Jan 2025 21:34:25 +0800 Subject: [PATCH 7/7] fix: add missing equals sign to git describe command (#10) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d876e3b..20fb761 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ NOW=$(shell date "+%F %T%:z") FLAGS=-s -w -X 'main.commit=$(COMMIT)' -X 'main.gover=$(GOVER)' -X 'main.date=$(NOW)' # set local vars without pipeline access -TAG=$(shell git describe --tags --abbrev 0) +TAG=$(shell git describe --tags --abbrev=0) ARCH=$(shell go env GOARCH) build: clean