-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
58f65a2
commit 8368895
Showing
6 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package charm | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/charmbracelet/bubbles/textinput" | ||
tea "github.com/charmbracelet/bubbletea" | ||
"github.com/charmbracelet/lipgloss" | ||
) | ||
|
||
type Input struct { | ||
text string | ||
placeholder string | ||
initialValue string | ||
hidden bool | ||
required bool | ||
} | ||
|
||
var ( | ||
SuccessStyle = lipgloss.NewStyle(). | ||
Foreground(lipgloss.Color("#00FF00")) | ||
|
||
ErrorStyle = lipgloss.NewStyle(). | ||
Foreground(lipgloss.Color("#FF0000")). // Red color | ||
Bold(true) | ||
|
||
inputStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) | ||
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("63")) | ||
) | ||
|
||
// Function to create and run the bubbletea model | ||
func GetSensitiveInput(placeholder string, defaultValue string) (string, error) { | ||
initialModel := model{ | ||
textInput: textinput.New(), | ||
} | ||
initialModel.textInput.Placeholder = placeholder | ||
initialModel.textInput.SetValue(defaultValue) | ||
initialModel.textInput.EchoMode = textinput.EchoPassword // Hides the input | ||
initialModel.textInput.Focus() | ||
|
||
p := tea.NewProgram(initialModel) | ||
finalModel, err := p.Run() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return finalModel.(model).textInput.Value(), nil | ||
} | ||
|
||
// Bubbletea model | ||
type model struct { | ||
textInput textinput.Model | ||
err error | ||
} | ||
|
||
func (m model) Init() tea.Cmd { | ||
return textinput.Blink | ||
} | ||
|
||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
switch msg := msg.(type) { | ||
case tea.KeyMsg: | ||
switch msg.Type { | ||
case tea.KeyEnter: | ||
if m.textInput.Value() == "" { | ||
m.err = fmt.Errorf("input is required") | ||
return m, nil | ||
} | ||
return m, tea.Quit | ||
case tea.KeyCtrlC: | ||
return m, tea.Quit | ||
} | ||
} | ||
var cmd tea.Cmd | ||
m.textInput, cmd = m.textInput.Update(msg) | ||
return m, cmd | ||
} | ||
|
||
func (m model) View() string { | ||
var errMsg string | ||
if m.err != nil { | ||
errMsg = ErrorStyle.Render(m.err.Error()) + "\n\n" | ||
} | ||
|
||
return fmt.Sprintf( | ||
"%s\n\n%s\n\n%s", | ||
titleStyle.Render("Enter your token:"), | ||
inputStyle.Render(m.textInput.View()), | ||
errMsg, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/qernal/cli-qernal/commands" | ||
) | ||
|
||
func main() { | ||
commands.RootCmd.Execute() | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package auth | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var AuthCmd = &cobra.Command{ | ||
Use: "auth", | ||
Short: "Manage your auth tokens", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
err := cmd.Help() | ||
if err != nil { | ||
return err | ||
} | ||
return errors.New("a valid subcommand is required") | ||
}, | ||
} | ||
|
||
func init() { | ||
|
||
AuthCmd.AddCommand(loginCmd) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package auth | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/qernal/cli-qernal/charm" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
type config struct { | ||
Token string `yaml:"token"` | ||
} | ||
|
||
var ( | ||
loginCmd = &cobra.Command{ | ||
Use: "login", | ||
Short: "Log in to your Qernal account", | ||
Long: `log in to your Qernal account by searching for the QERNAL_TOKEN environment variable first. | ||
the order in which values are searched for: | ||
1. **QERNAL_TOKEN environment variable:** If set, this is used as the token. | ||
2. **$HOME/.qernal/config.yaml file:** If the environment variable is not found, the CLI checks for the token in this file. | ||
3. **User input:** If neither of the above is found, the user is prompted to enter their Qernal token.`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
token, err := getQernalToken() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return saveConfig(token) | ||
}, | ||
} | ||
|
||
cfgPath = filepath.Join(os.Getenv("HOME"), ".qernal", "config.yaml") | ||
) | ||
|
||
func getQernalToken() (string, error) { | ||
// 1. Check environment variable | ||
if token := os.Getenv("QERNAL_TOKEN"); token != "" { | ||
fmt.Println(charm.SuccessStyle.Render("configuring CLI using environment variable ✅")) | ||
|
||
return token, nil | ||
} | ||
|
||
// 2. Check config file | ||
if token, err := readConfig(cfgPath); err == nil { | ||
fmt.Println(charm.SuccessStyle.Render(fmt.Sprintf("Using token from %s.", cfgPath))) | ||
return token, nil | ||
} else if os.IsNotExist(err) { | ||
// File doesn't exist, continue to prompt user | ||
token, err := charm.GetSensitiveInput("clientid@clientsecret", ".....") | ||
if err != nil { | ||
fmt.Println(charm.ErrorStyle.Render(fmt.Sprintf("error retrieving input %s", err.Error()))) | ||
return "", err | ||
} | ||
return token, nil | ||
|
||
} | ||
token, err := charm.GetSensitiveInput("Enter your token", ".....") | ||
if err != nil { | ||
fmt.Println(charm.ErrorStyle.Render(fmt.Sprintf("error retrieving input %s", err.Error()))) | ||
return "", err | ||
} | ||
return token, nil | ||
} | ||
|
||
func readConfig(cfgPath string) (string, error) { | ||
viper.SetConfigFile(cfgPath) | ||
|
||
// Read the config file | ||
if err := viper.ReadInConfig(); err != nil { | ||
return "", fmt.Errorf("error reading config file, %s", err) | ||
} | ||
|
||
// Unmarshal the config into a struct | ||
var cfg config | ||
if err := viper.Unmarshal(&cfg); err != nil { | ||
return "", fmt.Errorf("unable to decode into struct, %v", err) | ||
} | ||
|
||
return cfg.Token, nil | ||
} | ||
|
||
func saveConfig(token string) error { | ||
cfg := &config{Token: token} | ||
data, err := yaml.Marshal(cfg) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := os.MkdirAll(filepath.Dir(cfgPath), 0755); err != nil { | ||
return err | ||
} | ||
return os.WriteFile(cfgPath, data, 0644) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package commands | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/qernal/cli-qernal/commands/auth" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var RootCmd = &cobra.Command{ | ||
Use: "qernal", | ||
Short: "CLI for interacting with qernal.com", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if len(args) == 0 { | ||
return cmd.Help() | ||
} | ||
return nil | ||
}, | ||
} | ||
|
||
func Execute() { | ||
err := RootCmd.Execute() | ||
if err != nil { | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func init() { | ||
RootCmd.AddCommand(auth.AuthCmd) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/mitchellh/go-homedir" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var ( | ||
|
||
// represents the current config | ||
Current Config | ||
) | ||
|
||
type ErrNoTokenFound struct{} | ||
|
||
func (m *ErrNoTokenFound) Error() string { | ||
return "no token found" | ||
} | ||
|
||
type Config struct { | ||
Token string `yaml:"token"` | ||
} | ||
|
||
func LoadConfig() { | ||
|
||
token, found := os.LookupEnv("QERNAL_TOKEN") | ||
|
||
if found && token != "" { | ||
Current.Token = token | ||
return | ||
} | ||
|
||
// lookup token in config | ||
home, err := homedir.Dir() | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
viper.SetConfigFile(fmt.Sprintf("%s/%s/%s", home, ".qernal", "config.yaml")) | ||
|
||
err = viper.ReadInConfig() | ||
if err != nil { | ||
fmt.Print(fmt.Errorf("unable to read qernal config", err).Error()) | ||
} | ||
|
||
token = viper.Get("token").(string) | ||
if Current.Token != "" { | ||
Current.Token = token | ||
return | ||
} | ||
|
||
// TODO: prompt user for token | ||
fmt.Println("no token found") | ||
} |