diff --git a/cmd/api_key.go b/cmd/api_key.go index 06fe7e8..409e88d 100644 --- a/cmd/api_key.go +++ b/cmd/api_key.go @@ -23,6 +23,9 @@ var apiKeyCmd = &cobra.Command{ return err } + if isJSONOutput() { + return printJSON(result) + } fmt.Printf("Valid API key for team: %s\n", result.TeamName) return nil }, diff --git a/cmd/login.go b/cmd/auth_login.go similarity index 88% rename from cmd/login.go rename to cmd/auth_login.go index 699cc31..8e8d0e6 100644 --- a/cmd/login.go +++ b/cmd/auth_login.go @@ -37,6 +37,9 @@ var loginCmd = &cobra.Command{ return err } + if isJSONOutput() { + return printJSON(Result{Success: true, Message: fmt.Sprintf("Authenticated as team: %s", result.TeamName)}) + } fmt.Printf("API key saved. Authenticated as team: %s\n", result.TeamName) return nil }, diff --git a/cmd/logout.go b/cmd/auth_logout.go similarity index 85% rename from cmd/logout.go rename to cmd/auth_logout.go index d814055..93cf8c8 100644 --- a/cmd/logout.go +++ b/cmd/auth_logout.go @@ -14,6 +14,9 @@ var logoutCmd = &cobra.Command{ if err := config.Delete(); err != nil { return err } + if isJSONOutput() { + return printJSON(Result{Success: true}) + } fmt.Println("Logged out.") return nil }, diff --git a/cmd/debug.go b/cmd/auth_status.go similarity index 73% rename from cmd/debug.go rename to cmd/auth_status.go index 8021d51..00da741 100644 --- a/cmd/debug.go +++ b/cmd/auth_status.go @@ -1,9 +1,7 @@ package cmd import ( - "encoding/json" "fmt" - "os" "github.com/loops-so/cli/internal/config" "github.com/spf13/cobra" @@ -18,12 +16,12 @@ var statusCmd = &cobra.Command{ return err } - out, err := json.MarshalIndent(cfg, "", " ") - if err != nil { - return err + if isJSONOutput() { + return printJSON(cfg) } - fmt.Fprintln(os.Stdout, string(out)) + fmt.Printf("API Key: %s\n", cfg.APIKey) + fmt.Printf("Endpoint: %s\n", cfg.EndpointURL) return nil }, } diff --git a/cmd/output.go b/cmd/output.go new file mode 100644 index 0000000..367c51c --- /dev/null +++ b/cmd/output.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "os" +) + +type outputFlag string + +func (o *outputFlag) Set(s string) error { + switch s { + case "text", "json": + *o = outputFlag(s) + return nil + default: + return fmt.Errorf("must be \"text\" or \"json\"") + } +} + +func (o *outputFlag) String() string { return string(*o) } +func (o *outputFlag) Type() string { return "format" } + +type Result struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` +} + +func isJSONOutput() bool { + return outputFormat == "json" +} + +func printJSON(v any) error { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(v) +} diff --git a/cmd/output_test.go b/cmd/output_test.go new file mode 100644 index 0000000..e7e97d2 --- /dev/null +++ b/cmd/output_test.go @@ -0,0 +1,22 @@ +package cmd + +import "testing" + +func TestOutputFlagSet(t *testing.T) { + for _, valid := range []string{"text", "json"} { + var f outputFlag + if err := f.Set(valid); err != nil { + t.Errorf("Set(%q) unexpected error: %v", valid, err) + } + if string(f) != valid { + t.Errorf("Set(%q) got %q", valid, f) + } + } + + for _, invalid := range []string{"yaml", "csv", ""} { + var f outputFlag + if err := f.Set(invalid); err == nil { + t.Errorf("Set(%q) expected error, got nil", invalid) + } + } +} diff --git a/cmd/root.go b/cmd/root.go index 1b74a80..24c52e9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,18 +4,20 @@ Copyright © 2026 NAME HERE package cmd import ( + "fmt" "os" "github.com/spf13/cobra" ) +var outputFormat outputFlag = "text" + var rootCmd = &cobra.Command{ - Use: "loops", - Short: "The official CLI for Loops (https://loops.so)", - Long: "The official CLI for Loops (https://loops.so)", - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Use: "loops", + Short: "The official CLI for Loops (https://loops.so)", + Long: "The official CLI for Loops (https://loops.so)", + SilenceErrors: true, + SilenceUsage: true, } // Execute adds all child commands to the root command and sets flags appropriately. @@ -23,6 +25,11 @@ var rootCmd = &cobra.Command{ func Execute() { err := rootCmd.Execute() if err != nil { + if isJSONOutput() { + printJSON(Result{Success: false, Message: err.Error()}) + } else { + fmt.Fprintln(os.Stderr, "Error:", err) + } os.Exit(1) } } @@ -36,5 +43,5 @@ func init() { // Cobra also supports local flags, which will only run // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.PersistentFlags().VarP(&outputFormat, "output", "o", "Output format (text, json)") } diff --git a/cmd/transactional.go b/cmd/transactional.go index 791750e..0cdf7bd 100644 --- a/cmd/transactional.go +++ b/cmd/transactional.go @@ -74,6 +74,13 @@ var transactionalListCmd = &cobra.Command{ return err } + if isJSONOutput() { + if emails == nil { + emails = []api.TransactionalEmail{} + } + return printJSON(emails) + } + if len(emails) == 0 { fmt.Println("No transactional emails found.") return nil @@ -133,6 +140,9 @@ var transactionalSendCmd = &cobra.Command{ return err } + if isJSONOutput() { + return printJSON(Result{Success: true}) + } fmt.Println("Sent.") return nil },