merge: refactor/cli

This commit is contained in:
王性驊 2026-04-03 17:46:37 +08:00
commit 8f1b7159ed
6 changed files with 984 additions and 0 deletions

196
cmd/cli/accounts.go Normal file
View File

@ -0,0 +1,196 @@
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
}

118
cmd/cli/args.go Normal file
View File

@ -0,0 +1,118 @@
package cmd
import "fmt"
type ParsedArgs struct {
Tailscale bool
Help bool
Login bool
AccountsList bool
Logout bool
AccountName string
Proxies []string
ResetHwid bool
DeepClean bool
DryRun bool
}
func ParseArgs(argv []string) (ParsedArgs, error) {
var args ParsedArgs
for i := 0; i < len(argv); i++ {
arg := argv[i]
switch arg {
case "login", "add-account":
args.Login = true
if i+1 < len(argv) && len(argv[i+1]) > 0 && argv[i+1][0] != '-' {
i++
args.AccountName = argv[i]
}
case "logout", "remove-account":
args.Logout = true
if i+1 < len(argv) && len(argv[i+1]) > 0 && argv[i+1][0] != '-' {
i++
args.AccountName = argv[i]
}
case "accounts", "list-accounts":
args.AccountsList = true
case "reset-hwid", "reset":
args.ResetHwid = true
case "--deep-clean":
args.DeepClean = true
case "--dry-run":
args.DryRun = true
case "--tailscale":
args.Tailscale = true
case "--help", "-h":
args.Help = true
default:
if len(arg) > len("--proxy=") && arg[:len("--proxy=")] == "--proxy=" {
raw := arg[len("--proxy="):]
parts := splitComma(raw)
for _, p := range parts {
if p != "" {
args.Proxies = append(args.Proxies, p)
}
}
} else {
return args, fmt.Errorf("Unknown argument: %s", arg)
}
}
}
return args, nil
}
func splitComma(s string) []string {
var result []string
start := 0
for i := 0; i <= len(s); i++ {
if i == len(s) || s[i] == ',' {
part := trim(s[start:i])
if part != "" {
result = append(result, part)
}
start = i + 1
}
}
return result
}
func trim(s string) string {
start := 0
end := len(s)
for start < end && (s[start] == ' ' || s[start] == '\t') {
start++
}
for end > start && (s[end-1] == ' ' || s[end-1] == '\t') {
end--
}
return s[start:end]
}
func PrintHelp(version string) {
fmt.Printf("cursor-api-proxy v%s\n\n", version)
fmt.Println("Usage:")
fmt.Println(" cursor-api-proxy [options]")
fmt.Println("")
fmt.Println("Commands:")
fmt.Println(" login [name] Log into a Cursor account (saved to ~/.cursor-api-proxy/accounts/)")
fmt.Println(" login [name] --proxy=... Same, but with a proxy from a comma-separated list")
fmt.Println(" logout <name> Remove a saved Cursor account")
fmt.Println(" accounts List saved accounts with plan info")
fmt.Println(" reset-hwid Reset Cursor machine/telemetry IDs (anti-ban)")
fmt.Println(" reset-hwid --deep-clean Also wipe session storage and cookies")
fmt.Println("")
fmt.Println("Options:")
fmt.Println(" --tailscale Bind to 0.0.0.0 for tailnet/LAN access")
fmt.Println(" -h, --help Show this help message")
}

125
cmd/cli/login.go Normal file
View File

@ -0,0 +1,125 @@
package cmd
import (
"bufio"
"cursor-api-proxy/internal/agent"
"cursor-api-proxy/internal/env"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"syscall"
"time"
)
var loginURLRe = regexp.MustCompile(`https://cursor\.com/loginDeepControl.*?redirectTarget=cli`)
func HandleLogin(accountName string, proxies []string) error {
e := env.OsEnvToMap()
loaded := env.LoadEnvConfig(e, "")
agentBin := loaded.AgentBin
if accountName == "" {
accountName = fmt.Sprintf("account-%d", time.Now().UnixMilli()%10000)
}
accountsDir := agent.AccountsDir()
configDir := filepath.Join(accountsDir, accountName)
dirWasNew := !fileExists(configDir)
if err := os.MkdirAll(accountsDir, 0755); err != nil {
return fmt.Errorf("failed to create accounts dir: %w", err)
}
if err := os.MkdirAll(configDir, 0755); err != nil {
return fmt.Errorf("failed to create config dir: %w", err)
}
fmt.Printf("Logging into Cursor account: %s\n", accountName)
fmt.Printf("Config: %s\n\n", configDir)
fmt.Println("Run the login command — complete the login in your browser.")
fmt.Println("")
cleanupDir := func() {
if dirWasNew {
_ = os.RemoveAll(configDir)
}
}
cmdEnv := make([]string, 0, len(e)+2)
for k, v := range e {
cmdEnv = append(cmdEnv, k+"="+v)
}
cmdEnv = append(cmdEnv, "CURSOR_CONFIG_DIR="+configDir)
cmdEnv = append(cmdEnv, "NO_OPEN_BROWSER=1")
child := exec.Command(agentBin, "login")
child.Env = cmdEnv
child.Stdin = os.Stdin
child.Stderr = os.Stderr
stdoutPipe, err := child.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to create stdout pipe: %w", err)
}
if err := child.Start(); err != nil {
cleanupDir()
if os.IsNotExist(err) {
return fmt.Errorf("could not find '%s'. Make sure the Cursor CLI is installed", agentBin)
}
return fmt.Errorf("error launching agent login: %w", err)
}
// Handle cancellation signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
go func() {
sig := <-sigCh
_ = child.Process.Kill()
cleanupDir()
if sig == syscall.SIGINT {
fmt.Println("\n\nLogin cancelled.")
}
os.Exit(0)
}()
defer signal.Stop(sigCh)
var stdoutBuf string
scanner := bufio.NewScanner(stdoutPipe)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
stdoutBuf += line + "\n"
if loginURLRe.MatchString(stdoutBuf) {
match := loginURLRe.FindString(stdoutBuf)
if match != "" {
fmt.Printf("\nOpen this URL in your browser (incognito recommended):\n%s\n\n", match)
}
}
}
if err := child.Wait(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
cleanupDir()
return fmt.Errorf("login failed with code %d", exitErr.ExitCode())
}
return err
}
// Cache keychain token for this account
token := agent.ReadKeychainToken()
if token != "" {
agent.WriteCachedToken(configDir, token)
}
fmt.Printf("\nAccount '%s' saved — it will be auto-discovered when you start the proxy.\n", accountName)
return nil
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}

261
cmd/cli/resethwid.go Normal file
View File

@ -0,0 +1,261 @@
package cmd
import (
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"github.com/google/uuid"
)
func sha256hex() string {
b := make([]byte, 32)
_, _ = rand.Read(b)
h := sha256.Sum256(b)
return hex.EncodeToString(h[:])
}
func sha512hex() string {
b := make([]byte, 64)
_, _ = rand.Read(b)
h := sha512.Sum512(b)
return hex.EncodeToString(h[:])
}
func newUUID() string {
return uuid.New().String()
}
func log(icon, msg string) {
fmt.Printf(" %s %s\n", icon, msg)
}
func getCursorGlobalStorage() string {
switch runtime.GOOS {
case "darwin":
home, _ := os.UserHomeDir()
return filepath.Join(home, "Library", "Application Support", "Cursor", "User", "globalStorage")
case "windows":
appdata := os.Getenv("APPDATA")
return filepath.Join(appdata, "Cursor", "User", "globalStorage")
default:
xdg := os.Getenv("XDG_CONFIG_HOME")
if xdg == "" {
home, _ := os.UserHomeDir()
xdg = filepath.Join(home, ".config")
}
return filepath.Join(xdg, "Cursor", "User", "globalStorage")
}
}
func getCursorRoot() string {
gs := getCursorGlobalStorage()
return filepath.Dir(filepath.Dir(gs))
}
func generateNewIDs() map[string]string {
return map[string]string{
"telemetry.machineId": sha256hex(),
"telemetry.macMachineId": sha512hex(),
"telemetry.devDeviceId": newUUID(),
"telemetry.sqmId": "{" + fmt.Sprintf("%s", newUUID()+"") + "}",
"storage.serviceMachineId": newUUID(),
}
}
func killCursor() {
log("", "Stopping Cursor processes...")
switch runtime.GOOS {
case "windows":
exec.Command("taskkill", "/F", "/IM", "Cursor.exe").Run()
default:
exec.Command("pkill", "-x", "Cursor").Run()
exec.Command("pkill", "-f", "Cursor.app").Run()
}
log("", "Cursor stopped (or was not running)")
}
func updateStorageJSON(storagePath string, ids map[string]string) {
if _, err := os.Stat(storagePath); os.IsNotExist(err) {
log("", fmt.Sprintf("storage.json not found: %s", storagePath))
return
}
if runtime.GOOS == "darwin" {
exec.Command("chflags", "nouchg", storagePath).Run()
exec.Command("chmod", "644", storagePath).Run()
}
data, err := os.ReadFile(storagePath)
if err != nil {
log("", fmt.Sprintf("storage.json read error: %v", err))
return
}
var obj map[string]interface{}
if err := json.Unmarshal(data, &obj); err != nil {
log("", fmt.Sprintf("storage.json parse error: %v", err))
return
}
for k, v := range ids {
obj[k] = v
}
out, err := json.MarshalIndent(obj, "", " ")
if err != nil {
log("", fmt.Sprintf("storage.json marshal error: %v", err))
return
}
if err := os.WriteFile(storagePath, out, 0644); err != nil {
log("", fmt.Sprintf("storage.json write error: %v", err))
return
}
log("", "storage.json updated")
}
func updateStateVscdb(dbPath string, ids map[string]string) {
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
log("", fmt.Sprintf("state.vscdb not found: %s", dbPath))
return
}
if runtime.GOOS == "darwin" {
exec.Command("chflags", "nouchg", dbPath).Run()
exec.Command("chmod", "644", dbPath).Run()
}
if err := updateVscdbPureGo(dbPath, ids); err != nil {
log("", fmt.Sprintf("state.vscdb error: %v", err))
} else {
log("", "state.vscdb updated")
}
}
func updateMachineIDFile(machineID, cursorRoot string) {
var candidates []string
if runtime.GOOS == "linux" {
candidates = []string{
filepath.Join(cursorRoot, "machineid"),
filepath.Join(cursorRoot, "machineId"),
}
} else {
candidates = []string{filepath.Join(cursorRoot, "machineId")}
}
filePath := candidates[0]
for _, c := range candidates {
if _, err := os.Stat(c); err == nil {
filePath = c
break
}
}
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
log("", fmt.Sprintf("machineId dir error: %v", err))
return
}
if runtime.GOOS == "darwin" {
if _, err := os.Stat(filePath); err == nil {
exec.Command("chflags", "nouchg", filePath).Run()
exec.Command("chmod", "644", filePath).Run()
}
}
if err := os.WriteFile(filePath, []byte(machineID+"\n"), 0644); err != nil {
log("", fmt.Sprintf("machineId write error: %v", err))
return
}
log("", fmt.Sprintf("machineId file updated (%s)", filepath.Base(filePath)))
}
var dirsToWipe = []string{
"Session Storage", "Local Storage", "IndexedDB", "Cache", "Code Cache",
"GPUCache", "Service Worker", "Network", "Cookies", "Cookies-journal",
}
func deepClean(cursorRoot string) {
log("", "Deep-cleaning session data...")
wiped := 0
for _, name := range dirsToWipe {
target := filepath.Join(cursorRoot, name)
if _, err := os.Stat(target); os.IsNotExist(err) {
continue
}
info, err := os.Stat(target)
if err != nil {
continue
}
if info.IsDir() {
if err := os.RemoveAll(target); err == nil {
wiped++
}
} else {
if err := os.Remove(target); err == nil {
wiped++
}
}
}
log("", fmt.Sprintf("Wiped %d cache/session items", wiped))
}
func HandleResetHwid(doDeepClean, dryRun bool) error {
fmt.Print("\nCursor HWID Reset\n\n")
fmt.Println(" Resets all machine / telemetry IDs so Cursor sees a fresh install.")
fmt.Print(" Cursor must be closed — it will be killed automatically.\n\n")
globalStorage := getCursorGlobalStorage()
cursorRoot := getCursorRoot()
if _, err := os.Stat(globalStorage); os.IsNotExist(err) {
fmt.Printf("Cursor config not found at:\n %s\n", globalStorage)
fmt.Println(" Make sure Cursor is installed and has been run at least once.")
os.Exit(1)
}
if dryRun {
fmt.Println(" [DRY RUN] Would reset IDs in:")
fmt.Printf(" %s\n", filepath.Join(globalStorage, "storage.json"))
fmt.Printf(" %s\n", filepath.Join(globalStorage, "state.vscdb"))
fmt.Printf(" %s\n", filepath.Join(cursorRoot, "machineId"))
return nil
}
killCursor()
time.Sleep(800 * time.Millisecond)
newIDs := generateNewIDs()
log("", "Generated new IDs:")
for k, v := range newIDs {
fmt.Printf(" %s: %s\n", k, v)
}
fmt.Println()
log("", "Updating storage.json...")
updateStorageJSON(filepath.Join(globalStorage, "storage.json"), newIDs)
log("", "Updating state.vscdb...")
updateStateVscdb(filepath.Join(globalStorage, "state.vscdb"), newIDs)
log("", "Updating machineId file...")
updateMachineIDFile(newIDs["telemetry.machineId"], cursorRoot)
if doDeepClean {
fmt.Println()
deepClean(cursorRoot)
}
fmt.Print("\nHWID reset complete. You can now restart Cursor.\n\n")
return nil
}

29
cmd/cli/sqlite.go Normal file
View File

@ -0,0 +1,29 @@
package cmd
import (
"database/sql"
"fmt"
_ "modernc.org/sqlite"
)
func updateVscdbPureGo(dbPath string, ids map[string]string) error {
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return fmt.Errorf("open db: %w", err)
}
defer db.Close()
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS ItemTable (key TEXT PRIMARY KEY, value TEXT NOT NULL)`)
if err != nil {
return fmt.Errorf("create table: %w", err)
}
for k, v := range ids {
_, err = db.Exec(`INSERT OR REPLACE INTO ItemTable (key, value) VALUES (?, ?)`, k, v)
if err != nil {
return fmt.Errorf("insert %s: %w", k, err)
}
}
return nil
}

255
cmd/cli/usage.go Normal file
View File

@ -0,0 +1,255 @@
package cmd
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
type ModelUsage struct {
NumRequests int `json:"numRequests"`
NumRequestsTotal int `json:"numRequestsTotal"`
NumTokens int `json:"numTokens"`
MaxTokenUsage *int `json:"maxTokenUsage"`
MaxRequestUsage *int `json:"maxRequestUsage"`
}
type UsageData struct {
StartOfMonth string `json:"startOfMonth"`
Models map[string]ModelUsage `json:"-"`
}
type StripeProfile struct {
MembershipType string `json:"membershipType"`
SubscriptionStatus string `json:"subscriptionStatus"`
DaysRemainingOnTrial *int `json:"daysRemainingOnTrial"`
IsTeamMember bool `json:"isTeamMember"`
IsYearlyPlan bool `json:"isYearlyPlan"`
}
func DecodeJWTPayload(token string) map[string]interface{} {
parts := strings.Split(token, ".")
if len(parts) < 2 {
return nil
}
padded := strings.ReplaceAll(parts[1], "-", "+")
padded = strings.ReplaceAll(padded, "_", "/")
data, err := base64.StdEncoding.DecodeString(padded + strings.Repeat("=", (4-len(padded)%4)%4))
if err != nil {
return nil
}
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return nil
}
return result
}
func TokenSub(token string) string {
payload := DecodeJWTPayload(token)
if payload == nil {
return ""
}
if sub, ok := payload["sub"].(string); ok {
return sub
}
return ""
}
func apiGet(path, token string) (map[string]interface{}, error) {
client := &http.Client{Timeout: 8 * time.Second}
req, err := http.NewRequest("GET", "https://api2.cursor.sh"+path, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return nil, nil
}
return result, nil
}
func FetchAccountUsage(token string) (*UsageData, error) {
raw, err := apiGet("/auth/usage", token)
if err != nil || raw == nil {
return nil, err
}
startOfMonth, _ := raw["startOfMonth"].(string)
usage := &UsageData{
StartOfMonth: startOfMonth,
Models: make(map[string]ModelUsage),
}
for k, v := range raw {
if k == "startOfMonth" {
continue
}
data, err := json.Marshal(v)
if err != nil {
continue
}
var mu ModelUsage
if err := json.Unmarshal(data, &mu); err == nil {
usage.Models[k] = mu
}
}
return usage, nil
}
func FetchStripeProfile(token string) (*StripeProfile, error) {
raw, err := apiGet("/auth/full_stripe_profile", token)
if err != nil || raw == nil {
return nil, err
}
profile := &StripeProfile{
MembershipType: fmt.Sprintf("%v", raw["membershipType"]),
SubscriptionStatus: fmt.Sprintf("%v", raw["subscriptionStatus"]),
IsTeamMember: raw["isTeamMember"] == true,
IsYearlyPlan: raw["isYearlyPlan"] == true,
}
if d, ok := raw["daysRemainingOnTrial"].(float64); ok {
di := int(d)
profile.DaysRemainingOnTrial = &di
}
return profile, nil
}
func DescribePlan(profile *StripeProfile) string {
if profile == nil {
return ""
}
switch profile.MembershipType {
case "free_trial":
days := 0
if profile.DaysRemainingOnTrial != nil {
days = *profile.DaysRemainingOnTrial
}
return fmt.Sprintf("Pro Trial (%dd left) — unlimited fast requests", days)
case "pro":
return "Pro — extended limits"
case "pro_plus":
return "Pro+ — extended limits"
case "ultra":
return "Ultra — extended limits"
case "free", "hobby":
return "Hobby (free) — limited agent requests"
default:
return fmt.Sprintf("%s · %s", profile.MembershipType, profile.SubscriptionStatus)
}
}
var modelLabels = map[string]string{
"gpt-4": "Fast Premium Requests",
"claude-sonnet-4-6": "Claude Sonnet 4.6",
"claude-sonnet-4-5-20250929-v1": "Claude Sonnet 4.5",
"claude-sonnet-4-20250514-v1": "Claude Sonnet 4",
"claude-opus-4-6-v1": "Claude Opus 4.6",
"claude-opus-4-5-20251101-v1": "Claude Opus 4.5",
"claude-opus-4-1-20250805-v1": "Claude Opus 4.1",
"claude-opus-4-20250514-v1": "Claude Opus 4",
"claude-haiku-4-5-20251001-v1": "Claude Haiku 4.5",
"claude-3-5-haiku-20241022-v1": "Claude 3.5 Haiku",
"gpt-5": "GPT-5",
"gpt-4o": "GPT-4o",
"o1": "o1",
"o3-mini": "o3-mini",
"cursor-small": "Cursor Small (free)",
}
func modelLabel(key string) string {
if label, ok := modelLabels[key]; ok {
return label
}
return key
}
func FormatUsageSummary(usage *UsageData) []string {
if usage == nil {
return nil
}
var lines []string
start := "?"
if usage.StartOfMonth != "" {
if t, err := time.Parse(time.RFC3339, usage.StartOfMonth); err == nil {
start = t.Format("2006-01-02")
} else {
start = usage.StartOfMonth
}
}
lines = append(lines, fmt.Sprintf(" Billing period from %s", start))
if len(usage.Models) == 0 {
lines = append(lines, " No requests this billing period")
return lines
}
type entry struct {
key string
usage ModelUsage
}
var entries []entry
for k, v := range usage.Models {
entries = append(entries, entry{k, v})
}
// Sort: entries with limits first, then by usage descending
for i := 1; i < len(entries); i++ {
for j := i; j > 0; j-- {
a, b := entries[j-1], entries[j]
aHasLimit := a.usage.MaxRequestUsage != nil
bHasLimit := b.usage.MaxRequestUsage != nil
if !aHasLimit && bHasLimit {
entries[j-1], entries[j] = entries[j], entries[j-1]
} else if aHasLimit == bHasLimit && a.usage.NumRequests < b.usage.NumRequests {
entries[j-1], entries[j] = entries[j], entries[j-1]
} else {
break
}
}
}
for _, e := range entries {
used := e.usage.NumRequests
max := e.usage.MaxRequestUsage
label := modelLabel(e.key)
if max != nil && *max > 0 {
pct := int(float64(used) / float64(*max) * 100)
bar := makeBar(used, *max, 12)
lines = append(lines, fmt.Sprintf(" %s: %d/%d (%d%%) [%s]", label, used, *max, pct, bar))
} else if used > 0 {
lines = append(lines, fmt.Sprintf(" %s: %d requests", label, used))
} else {
lines = append(lines, fmt.Sprintf(" %s: 0 requests (unlimited)", label))
}
}
return lines
}
func makeBar(used, max, width int) string {
fill := int(float64(used) / float64(max) * float64(width))
if fill > width {
fill = width
}
return strings.Repeat("█", fill) + strings.Repeat("░", width-fill)
}