197 lines
4.8 KiB
Go
197 lines
4.8 KiB
Go
package cmd
|
|
|
|
import (
|
|
"cursor-api-proxy/internal/agent"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type AccountInfo struct {
|
|
Name string
|
|
ConfigDir string
|
|
Authenticated bool
|
|
Email string
|
|
DisplayName string
|
|
AuthID string
|
|
Plan string
|
|
SubscriptionStatus string
|
|
ExpiresAt string
|
|
}
|
|
|
|
func ReadAccountInfo(name, configDir string) AccountInfo {
|
|
info := AccountInfo{Name: name, ConfigDir: configDir}
|
|
|
|
configFile := filepath.Join(configDir, "cli-config.json")
|
|
data, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
return info
|
|
}
|
|
|
|
var raw struct {
|
|
AuthInfo *struct {
|
|
Email string `json:"email"`
|
|
DisplayName string `json:"displayName"`
|
|
AuthID string `json:"authId"`
|
|
} `json:"authInfo"`
|
|
}
|
|
if err := json.Unmarshal(data, &raw); err == nil && raw.AuthInfo != nil {
|
|
info.Authenticated = true
|
|
info.Email = raw.AuthInfo.Email
|
|
info.DisplayName = raw.AuthInfo.DisplayName
|
|
info.AuthID = raw.AuthInfo.AuthID
|
|
}
|
|
|
|
statsigFile := filepath.Join(configDir, "statsig-cache.json")
|
|
statsigData, err := os.ReadFile(statsigFile)
|
|
if err != nil {
|
|
return info
|
|
}
|
|
|
|
var statsigWrapper struct {
|
|
Data string `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(statsigData, &statsigWrapper); err != nil || statsigWrapper.Data == "" {
|
|
return info
|
|
}
|
|
|
|
var statsig struct {
|
|
User *struct {
|
|
Custom *struct {
|
|
IsEnterpriseUser bool `json:"isEnterpriseUser"`
|
|
StripeSubscriptionStatus string `json:"stripeSubscriptionStatus"`
|
|
StripeMembershipExpiration string `json:"stripeMembershipExpiration"`
|
|
} `json:"custom"`
|
|
} `json:"user"`
|
|
}
|
|
if err := json.Unmarshal([]byte(statsigWrapper.Data), &statsig); err != nil {
|
|
return info
|
|
}
|
|
|
|
if statsig.User != nil && statsig.User.Custom != nil {
|
|
c := statsig.User.Custom
|
|
if c.IsEnterpriseUser {
|
|
info.Plan = "Enterprise"
|
|
} else if c.StripeSubscriptionStatus == "active" {
|
|
info.Plan = "Pro"
|
|
} else {
|
|
info.Plan = "Free"
|
|
}
|
|
info.SubscriptionStatus = c.StripeSubscriptionStatus
|
|
info.ExpiresAt = c.StripeMembershipExpiration
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
func HandleAccountsList() error {
|
|
accountsDir := agent.AccountsDir()
|
|
|
|
entries, err := os.ReadDir(accountsDir)
|
|
if err != nil {
|
|
fmt.Println("No accounts found. Use 'cursor-api-proxy login' to add one.")
|
|
return nil
|
|
}
|
|
|
|
var names []string
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
names = append(names, e.Name())
|
|
}
|
|
}
|
|
|
|
if len(names) == 0 {
|
|
fmt.Println("No accounts found. Use 'cursor-api-proxy login' to add one.")
|
|
return nil
|
|
}
|
|
|
|
fmt.Print("Cursor Accounts:\n\n")
|
|
|
|
keychainToken := agent.ReadKeychainToken()
|
|
|
|
for i, name := range names {
|
|
configDir := filepath.Join(accountsDir, name)
|
|
info := ReadAccountInfo(name, configDir)
|
|
|
|
fmt.Printf(" %d. %s\n", i+1, name)
|
|
|
|
if info.Authenticated {
|
|
cachedToken := agent.ReadCachedToken(configDir)
|
|
keychainMatchesAccount := keychainToken != "" && info.AuthID != "" && TokenSub(keychainToken) == info.AuthID
|
|
token := cachedToken
|
|
if token == "" && keychainMatchesAccount {
|
|
token = keychainToken
|
|
}
|
|
|
|
var liveProfile *StripeProfile
|
|
var liveUsage *UsageData
|
|
if token != "" {
|
|
liveUsage, _ = FetchAccountUsage(token)
|
|
liveProfile, _ = FetchStripeProfile(token)
|
|
}
|
|
|
|
if info.Email != "" {
|
|
display := ""
|
|
if info.DisplayName != "" {
|
|
display = " (" + info.DisplayName + ")"
|
|
}
|
|
fmt.Printf(" %s%s\n", info.Email, display)
|
|
}
|
|
|
|
if info.Plan != "" && liveProfile == nil {
|
|
canceled := ""
|
|
if info.SubscriptionStatus == "canceled" {
|
|
canceled = " · canceled"
|
|
}
|
|
expiry := ""
|
|
if info.ExpiresAt != "" {
|
|
expiry = " · expires " + info.ExpiresAt
|
|
}
|
|
fmt.Printf(" %s%s%s\n", info.Plan, canceled, expiry)
|
|
}
|
|
fmt.Println(" Authenticated")
|
|
|
|
if liveProfile != nil {
|
|
fmt.Printf(" %s\n", DescribePlan(liveProfile))
|
|
}
|
|
if liveUsage != nil {
|
|
for _, line := range FormatUsageSummary(liveUsage) {
|
|
fmt.Println(line)
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Println(" Not authenticated")
|
|
}
|
|
|
|
fmt.Println("")
|
|
}
|
|
|
|
fmt.Println("Tip: run 'cursor-api-proxy logout <name>' to remove an account.")
|
|
return nil
|
|
}
|
|
|
|
func HandleLogout(accountName string) error {
|
|
if accountName == "" {
|
|
fmt.Fprintln(os.Stderr, "Error: Please specify the account name to remove.")
|
|
fmt.Fprintln(os.Stderr, "Usage: cursor-api-proxy logout <account-name>")
|
|
os.Exit(1)
|
|
}
|
|
|
|
accountsDir := agent.AccountsDir()
|
|
configDir := filepath.Join(accountsDir, accountName)
|
|
|
|
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
|
fmt.Fprintf(os.Stderr, "Account '%s' not found.\n", accountName)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := os.RemoveAll(configDir); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error removing account: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Account '%s' removed.\n", accountName)
|
|
return nil
|
|
}
|