From 82ffe7ec557a0309fb41b2089cfe40f285a2e55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Br=C3=BCggemann?= Date: Mon, 2 Sep 2024 22:30:58 +0200 Subject: [PATCH 1/3] FEATURE: Allow user to select vault when starting the cli for the first time --- cmd/root.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/cmd/root.go b/cmd/root.go index 6af8019..ee6fc19 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,10 +1,13 @@ package cmd import ( + "encoding/json" "fmt" + tea "github.com/charmbracelet/bubbletea" "io" "log/slog" "os" + "os/exec" "path/filepath" "github.com/charmbracelet/lipgloss" @@ -26,6 +29,22 @@ const ( ` ) +type Vault struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type VaultSelectionModel struct { + vaults []Vault + cursor int + choice int + resetView bool +} + +func (m VaultSelectionModel) Init() tea.Cmd { + return nil +} + var ( // cliVersion is the version of the software. // Typically a branch or tag name. @@ -97,12 +116,101 @@ func init() { } } +func (m VaultSelectionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "up": + if m.cursor > 0 { + m.cursor-- + } + case "down": + if m.cursor < len(m.vaults)-1 { + m.cursor++ + } + case "enter": + m.choice = m.cursor + m.resetView = true + return m, tea.Quit + } + } + return m, nil +} + +func (m VaultSelectionModel) View() string { + if m.resetView { + return "\033[H\033[2J" + } + + s := "Select the 1password vault that should be used with buchhalter:\n\n" + for i, vault := range m.vaults { + cursor := " " + if m.cursor == i { + cursor = ">" + } + s += fmt.Sprintf("%s %s\n", cursor, vault.Name) + } + return s +} + +func getVaults() ([]Vault, error) { + cmd := exec.Command("op", "vault", "list", "--format", "json") + output, err := cmd.Output() + if err != nil { + return nil, err + } + + var vaults []Vault + err = json.Unmarshal(output, &vaults) + if err != nil { + return nil, err + } + + return vaults, nil +} + +func selectVault(vaults []Vault) (Vault, error) { + model := VaultSelectionModel{vaults: vaults} + p := tea.NewProgram(model) + if err := p.Start(); err != nil { + return Vault{}, err + } + return model.vaults[model.choice], nil +} + func initConfig() { homeDir, _ := os.UserHomeDir() buchhalterConfigDir := filepath.Join(homeDir, ".buchhalter") configFile := filepath.Join(buchhalterConfigDir, ".buchhalter.yaml") buchhalterDir := filepath.Join(homeDir, "buchhalter") + if _, err := os.Stat(configFile); os.IsNotExist(err) { + err := utils.CreateDirectoryIfNotExists(buchhalterConfigDir) + if err != nil { + fmt.Println("Error creating config directory:", err) + os.Exit(1) + } + + vaults, err := getVaults() + if err != nil { + fmt.Println("Error listing vaults:", err) + os.Exit(1) + } + + selectedVault, err := selectVault(vaults) + if err != nil { + fmt.Println("Error selecting vault:", err) + os.Exit(1) + } + + viper.Set("credential_provider_vault", selectedVault.Name) + err = viper.WriteConfigAs(configFile) + if err != nil { + fmt.Println("Error creating config file:", err) + os.Exit(1) + } + } + // Set default values for viper config // Documented settings viper.SetDefault("credential_provider_cli_command", "") From 6089fd7f248575562525dc35b5df92036c9c8053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Br=C3=BCggemann?= Date: Tue, 3 Sep 2024 07:50:21 +0200 Subject: [PATCH 2/3] BUGFIX: Fix vault name selector and unify styling --- cmd/root.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ee6fc19..5841a3d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -116,7 +116,7 @@ func init() { } } -func (m VaultSelectionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *VaultSelectionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { @@ -142,13 +142,14 @@ func (m VaultSelectionModel) View() string { return "\033[H\033[2J" } - s := "Select the 1password vault that should be used with buchhalter:\n\n" + s := "Select the 1password vault that should be used with buchhalter cli:\n\n" for i, vault := range m.vaults { - cursor := " " if m.cursor == i { - cursor = ">" + s += "(•) " + } else { + s += "( ) " } - s += fmt.Sprintf("%s %s\n", cursor, vault.Name) + s += vault.Name + "\n" } return s } @@ -171,8 +172,8 @@ func getVaults() ([]Vault, error) { func selectVault(vaults []Vault) (Vault, error) { model := VaultSelectionModel{vaults: vaults} - p := tea.NewProgram(model) - if err := p.Start(); err != nil { + p := tea.NewProgram(&model) + if _, err := p.Run(); err != nil { return Vault{}, err } return model.vaults[model.choice], nil @@ -193,7 +194,7 @@ func initConfig() { vaults, err := getVaults() if err != nil { - fmt.Println("Error listing vaults:", err) + fmt.Println("Error listing vaults. Make sure 1password cli is installed and you are logged in with eval $(op signin)", err) os.Exit(1) } @@ -214,7 +215,6 @@ func initConfig() { // Set default values for viper config // Documented settings viper.SetDefault("credential_provider_cli_command", "") - viper.SetDefault("credential_provider_vault", "Base") viper.SetDefault("credential_provider_item_tag", "buchhalter-ai") viper.SetDefault("buchhalter_directory", buchhalterDir) viper.SetDefault("buchhalter_config_directory", buchhalterConfigDir) From dcd1c004f6fa6b04533e838478641e58ec527f91 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 8 Sep 2024 17:51:59 +0200 Subject: [PATCH 3/3] Reorder imports and set TODOs for later refactoring --- cmd/root.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 5841a3d..19b8b09 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,13 +3,13 @@ package cmd import ( "encoding/json" "fmt" - tea "github.com/charmbracelet/bubbletea" "io" "log/slog" "os" "os/exec" "path/filepath" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -154,6 +154,7 @@ func (m VaultSelectionModel) View() string { return s } +// TODO Refactor and move to vault.1password.go func getVaults() ([]Vault, error) { cmd := exec.Command("op", "vault", "list", "--format", "json") output, err := cmd.Output() @@ -185,6 +186,7 @@ func initConfig() { configFile := filepath.Join(buchhalterConfigDir, ".buchhalter.yaml") buchhalterDir := filepath.Join(homeDir, "buchhalter") + // TODO We check the existing part of the configuration file here and below -> refctor to only once if _, err := os.Stat(configFile); os.IsNotExist(err) { err := utils.CreateDirectoryIfNotExists(buchhalterConfigDir) if err != nil {