126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
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
|
|
}
|