package cli import ( "context" "fmt" "os" "strconv" "strings" "text/tabwriter" "github.com/spf13/cobra" ) func NewAccountCmd() *cobra.Command { cmd := &cobra.Command{ Use: "account", Short: "Manage your TermCloud account", Long: "Create, authenticate, and manage your TermCloud account", } cmd.AddCommand(newAccountCreateCmd()) cmd.AddCommand(newAccountLoginCmd()) cmd.AddCommand(newAccountInfoCmd()) cmd.AddCommand(newAccountTopUpCmd()) cmd.AddCommand(newAccountPaymentsCmd()) cmd.AddCommand(newAccountUsageCmd()) return cmd } func newAccountCreateCmd() *cobra.Command { return &cobra.Command{ Use: "create", Short: "Create a new TermCloud account", RunE: func(cmd *cobra.Command, args []string) error { config, err := LoadConfig() if err != nil { return fmt.Errorf("failed to load config: %w", err) } client := NewClient(config.ServerURL, "") ctx := context.Background() account, err := client.CreateAccount(ctx) if err != nil { return fmt.Errorf("failed to create account: %w", err) } config.AccountNumber = account.AccountNumber config.AccessToken = account.AccessToken if err := config.Save(); err != nil { return fmt.Errorf("failed to save config: %w", err) } fmt.Printf("Account created successfully!\n") fmt.Printf("Account Number: %s\n", account.AccountNumber) fmt.Printf("Access Token: %s\n", account.AccessToken) fmt.Printf("Status: %s\n", getAccountStatus(account)) fmt.Printf("\nIMPORTANT: Save your account number and access token!\n") fmt.Printf("You are now logged in. Add funds using: tcman account top-up 1\n") return nil }, } } func newAccountLoginCmd() *cobra.Command { return &cobra.Command{ Use: "login ", Short: "Login to an existing account", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { accountNumber := args[0] accessToken := args[1] config, err := LoadConfig() if err != nil { return fmt.Errorf("failed to load config: %w", err) } client := NewClient(config.ServerURL, accessToken) ctx := context.Background() account, err := client.GetAccount(ctx) if err != nil { return fmt.Errorf("failed to authenticate: %w", err) } if account.AccountNumber != accountNumber { return fmt.Errorf("account number mismatch") } config.AccountNumber = accountNumber config.AccessToken = accessToken if err := config.Save(); err != nil { return fmt.Errorf("failed to save config: %w", err) } fmt.Printf("Successfully logged in!\n") fmt.Printf("Account: %s\n", account.AccountNumber) fmt.Printf("Status: %s\n", getAccountStatus(account)) fmt.Printf("Balance: $%.2f USD\n", account.BalanceUSD) return nil }, } } func newAccountInfoCmd() *cobra.Command { return &cobra.Command{ Use: "info", Short: "Show account information", RunE: func(cmd *cobra.Command, args []string) error { config, err := LoadConfig() if err != nil { return fmt.Errorf("failed to load config: %w", err) } if !config.IsAuthenticated() { return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'") } client := NewClient(config.ServerURL, config.AccessToken) ctx := context.Background() account, err := client.GetAccount(ctx) if err != nil { return fmt.Errorf("failed to get account info: %w", err) } fmt.Printf("Account Information:\n") fmt.Printf(" Number: %s\n", account.AccountNumber) fmt.Printf(" Status: %s\n", getAccountStatus(account)) fmt.Printf(" Balance: $%.2f USD\n", account.BalanceUSD) fmt.Printf(" Created: %s\n", account.CreatedAt.Format("2006-01-02 15:04:05")) if account.ActivatedAt != nil { fmt.Printf(" Activated: %s\n", account.ActivatedAt.Format("2006-01-02 15:04:05")) } fmt.Printf(" Last Billing: %s\n", account.LastBillingDate.Format("2006-01-02")) return nil }, } } func newAccountTopUpCmd() *cobra.Command { return &cobra.Command{ Use: "top-up [account-number]", Short: "Add funds to your account via Bitcoin", Long: "Add funds to your account. If logged in, uses your account. If not logged in, provide account number.", Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { amount, err := strconv.ParseFloat(args[0], 64) if err != nil { return fmt.Errorf("invalid amount: %w", err) } if amount < 1.0 { return fmt.Errorf("minimum top-up amount is $1.00 USD") } config, err := LoadConfig() if err != nil { return fmt.Errorf("failed to load config: %w", err) } var accountNumber string if len(args) == 2 { accountNumber = args[1] } else if config.IsAuthenticated() { accountNumber = config.AccountNumber } else if config.AccountNumber != "" { accountNumber = config.AccountNumber } else { return fmt.Errorf("account number required. Usage: tcman account top-up ") } client := NewClient(config.ServerURL, "") ctx := context.Background() payment, err := client.CreatePaymentByAccountNumber(ctx, accountNumber, amount) if err != nil { return fmt.Errorf("failed to create payment: %w", err) } fmt.Printf("Bitcoin Payment Created:\n") fmt.Printf(" Payment ID: %d\n", payment.ID) fmt.Printf(" Account: %s\n", accountNumber) fmt.Printf(" Amount: $%.2f USD (%.8f BTC)\n", payment.USDAmount, payment.BTCAmount) fmt.Printf(" Address: %s\n", payment.BTCAddress) fmt.Printf(" Status: %s\n", strings.ToUpper(payment.Status)) if payment.ExpiresAt != nil { fmt.Printf(" Expires: %s\n", payment.ExpiresAt.Format("2006-01-02 15:04:05")) } fmt.Printf("\nSend exactly %.8f BTC to the address above.\n", payment.BTCAmount) fmt.Printf("Your payment will be confirmed manually after Bitcoin is received.\n") return nil }, } } func newAccountPaymentsCmd() *cobra.Command { return &cobra.Command{ Use: "payments", Short: "List payment history", RunE: func(cmd *cobra.Command, args []string) error { config, err := LoadConfig() if err != nil { return fmt.Errorf("failed to load config: %w", err) } if !config.IsAuthenticated() { return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'") } client := NewClient(config.ServerURL, config.AccessToken) ctx := context.Background() payments, err := client.GetPayments(ctx) if err != nil { return fmt.Errorf("failed to get payments: %w", err) } if len(payments) == 0 { fmt.Printf("No payments found.\n") return nil } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "ID\tAmount\tBTC Amount\tStatus\tCreated\tConfirmed") fmt.Fprintln(w, "---\t------\t----------\t------\t-------\t---------") for _, payment := range payments { confirmedStr := "N/A" if payment.ConfirmedAt != nil { confirmedStr = payment.ConfirmedAt.Format("2006-01-02 15:04") } fmt.Fprintf(w, "%d\t$%.2f\t%.8f\t%s\t%s\t%s\n", payment.ID, payment.USDAmount, payment.BTCAmount, strings.ToUpper(payment.Status), payment.CreatedAt.Format("2006-01-02 15:04"), confirmedStr, ) } w.Flush() return nil }, } } func newAccountUsageCmd() *cobra.Command { return &cobra.Command{ Use: "usage", Short: "Show billing and usage history", RunE: func(cmd *cobra.Command, args []string) error { fmt.Printf("Usage tracking is handled automatically.\n") fmt.Printf("You are charged monthly based on peak storage usage.\n") fmt.Printf("Current rate: $0.50 per GB per month.\n") fmt.Printf("\nUse 'tcman account info' to see your current balance.\n") return nil }, } } func getAccountStatus(account *Account) string { if !account.IsActive { return "INACTIVE (needs funds)" } if account.BalanceUSD <= 0 { return "LOW BALANCE" } return "ACTIVE" }