package agent import ( "bytes" "context" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "chat-agent-system/internal/config" "chat-agent-system/internal/workspace" ) type CursorCLIBackend struct { cfg *config.Config } func (c *CursorCLIBackend) Name() string { return "cursor-cli" } func (c *CursorCLIBackend) Execute(ctx context.Context, req Request) (*Result, error) { if len(req.Targets) == 0 { req.Targets = []string{workspace.DeliverableKey} } workDir := req.WorkDir if !filepath.IsAbs(workDir) { wd, _ := os.Getwd() workDir = filepath.Join(wd, workDir) } payload, _ := json.Marshal(map[string]any{ "workspace": workDir, "api_key": c.cfg.CursorAPIKey, "model": c.cfg.CursorModel, "prompt": req.Prompt, "instructions": req.Instructions, "targets": req.Targets, }) bridgeBin := c.cfg.CursorBridgeBin if !filepath.IsAbs(bridgeBin) { wd, _ := os.Getwd() bridgeBin = filepath.Join(wd, bridgeBin) } timeout := req.Timeout if timeout <= 0 { timeout = c.cfg.AgentTimeout } runCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() cmd := exec.CommandContext(runCtx, bridgeBin) cmd.Dir = workDir cmd.Stdin = bytes.NewReader(payload) cmd.Env = append(os.Environ(), "CURSOR_API_KEY="+c.cfg.CursorAPIKey, "CURSOR_MODEL="+c.cfg.CursorModel, ) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { return &Result{ Success: false, ErrorReason: fmt.Sprintf("cursor execution error: %v, stderr: %s", err, stderr.String()), }, nil } type bridgeResponse struct { OK bool `json:"ok"` FallbackReason string `json:"fallback_reason"` Stderr string `json:"stderr"` } var bResp bridgeResponse lines := strings.Split(strings.TrimSpace(stdout.String()), "\n") if len(lines) > 0 { _ = json.Unmarshal([]byte(lines[len(lines)-1]), &bResp) } if !bResp.OK { errMsg := bResp.FallbackReason if bResp.Stderr != "" { errMsg += " (" + bResp.Stderr + ")" } if errMsg == "" { errMsg = "cursor bridge reported task failed" } return &Result{ Success: false, ErrorReason: errMsg, Logs: stdout.String(), }, nil } patches := make(map[string][]byte) for _, t := range req.Targets { p := filepath.Join(workDir, t) if b, err := os.ReadFile(p); err == nil { patches[t] = b } } return &Result{ Success: true, Patches: patches, Logs: stdout.String(), }, nil }