opencode-cursor-agent/cmd/login.go

126 lines
3.0 KiB
Go
Raw Normal View History

2026-03-30 14:09:15 +00:00
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
}