Skip to content

Add Cursor as a third agent under the CodingAgent protocol (mirrors PR #197 Phase C) #417

@dgershman

Description

@dgershman

Lands a Cursor agent for Crow on the existing CodingAgent protocol that PR #197 established. Mirrors the Packages/CrowCodex/ pattern but reuses Packages/CrowClaude/-shaped implementations for the hook layer because Cursor's hook engine is a superset of Claude's, not a no-op like Codex's per-session writer.

Supersedes #401 (the broad investigation — Phases A & B from #197 already answered most of it; this ticket scopes the remaining Phase-C-equivalent work for Cursor specifically).

What's already in place from PR #197

  • CrowCore/Agent/* protocols: CodingAgent, AgentKind, AgentRegistry, StateSignalSource, AgentStateTransition, HookConfigWriter, AgentHookEvent.
  • Per-session selection: Session.agentKind, AppConfig.defaultAgentKind, picker in CreateSessionView + Settings, crow new-session --agent <kind>, RPC agent_kind.
  • Registration gating on binary presence (Codex pattern at app launch).
  • AGENTS.md template at Resources/AGENTS.md.template (written by CodexScaffolder today; reused here).

So this ticket is Phase C analog only — a new Packages/CrowCursor/ package and the minimal wiring to register it.

CLI surface (from docs.cursor.com)

  • Binary is agent, not cursor.
  • Interactive: agent. Initial prompt as positional: agent "refactor auth".
  • Headless / print mode: agent -p "<prompt>" --output-format <text|json|stream-json> with optional --stream-partial-output. --force / --yolo enables file modifications.
  • Resume / continuation: agent --continue, agent --resume="<chat-id>", agent ls, agent resume. Better parity than Codex.
  • Auth: CURSOR_API_KEY env var for scripts; GUI-stored creds inherited otherwise.
  • Sandbox: --sandbox enabled|disabled (Cursor-specific).
  • Built-in slash commands: /plan, /ask, /sandbox, /max-mode [on|off].

Hook surface (https://cursor.com/docs/hooks)

Superset of Claude Code's hook engine. 20+ events:

  • Agent: sessionStart, sessionEnd, preToolUse, postToolUse, postToolUseFailure, subagentStart, subagentStop, beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution, beforeReadFile, afterFileEdit, beforeSubmitPrompt, preCompact, stop, afterAgentResponse, afterAgentThought.
  • Tab: beforeTabFileRead, afterTabFileEdit.
  • App lifecycle: workspaceOpen.

Config layered Enterprise → Team → Project → User. Project hooks live at <project>/.cursor/hooks.json; user hooks at ~/.cursor/hooks.json. Stdin JSON in, stdout JSON out. Exit code 0/2 convention "matches Claude Code behavior for compatibility." Cursor sets CLAUDE_PROJECT_DIR as an alias of CURSOR_PROJECT_DIR "for Claude compatibility" and "supports loading hooks from third-party tools like Claude Code."

Differences from Claude:

  • Event names are camelCase (preToolUse) vs PascalCase (PreToolUse). Mapping layer needed.
  • Cursor splits tool gating into generic (preToolUse) + specialized (beforeShellExecution/beforeMCPExecution/beforeReadFile). More granular state available.
  • Field correlation uses conversation_id + generation_id rather than Claude's session ids.

Skills compat (https://cursor.com/docs/skills)

Cursor reads .claude/skills/, .codex/skills/, ~/.claude/skills/, ~/.codex/skills/ directly for compat. Crow's existing .claude/skills/* directory (/crow-create-ticket, /crow-workspace, /crow-review-pr, etc.) works in Cursor sessions out of the box. Schema: SKILL.md + YAML frontmatter (name, description, paths, disable-model-invocation). No scaffolder work required for slash commands.

Config / paths

  • Global CLI config: ~/.cursor/cli-config.json (pure JSON, no comments).
  • Project CLI config: <project>/.cursor/cli.json (permissions only — everything else global).
  • Override via CURSOR_CONFIG_DIR env or XDG_CONFIG_HOME on Linux.

Project rules (instruction file)

  • .cursor/rules/*.mdc with frontmatter alwaysApply, description, globs — modern.
  • AGENTS.md — simple alternative; same file Codex uses; reuse the existing Resources/AGENTS.md.template.
  • .cursorrules — silent in docs (presumed legacy; don't bother).

Implementation plan

Packages/CrowCursor/ (new package; mirrors Packages/CrowCodex/ shape)

  • CursorAgentdisplayName: "Cursor", iconSystemName, launchCommandToken: "agent", findBinary(), autoLaunchCommand(...) that resolves to agent (interactive) / agent -p (one-shot) / agent --continue / agent --resume=<id> based on session state. supportsRemoteControl = true (hook engine + stop.followup_message for auto-continue).
  • CursorHookConfigWriter — writes ~/.cursor/hooks.json at app launch (global, same single-file pattern as CodexHookConfigWriter). Hooks resolved on Cursor's side use CLAUDE_PROJECT_DIR alias so Crow's per-worktree resolution still works. Per-worktree project-level hooks at <worktree>/.cursor/hooks.json if needed; start with global-only for MVP.
  • CursorSignalSource — subscribes to a curated event subset: sessionStart, preToolUse, postToolUse, postToolUseFailure, beforeShellExecution, afterShellExecution, beforeSubmitPrompt, stop, afterAgentResponse. CamelCase ↔ PascalCase mapping layer. Use conversation_id as the session correlation key. Same exit-code 0/2 protocol.
  • CursorScaffolder — reuses Resources/AGENTS.md.template already shipped by CrowCodex. Preserves the user's ## Known Issues / Corrections section. Optionally writes a .cursor/rules/crow.mdc per-worktree pointing back at AGENTS.md (TBD — empirical: do Cursor rules add anything Crow needs that AGENTS.md doesn't already give us?).
  • CursorLauncher, CursorNotifyPayload — bridge Cursor hook payloads → AgentHookEvent. For MVP, the notify channel can piggyback on the same crow hook-event --agent CLI used by Codex; new crow cursor-notify subcommand only if Cursor's emit shape diverges from what hook-event already accepts.

Wiring in AppDelegate / SessionService

  • Register CursorAgent at app launch alongside ClaudeCodeAgent and OpenAICodexAgent, gated on findBinary() returning a real path. No change if agent isn't installed.
  • SessionService.launchAgent already dispatches via agent.autoLaunchCommand after Phases A + B + C: agent abstraction + OpenAI Codex MVP #197 — no per-agent special-casing needed.
  • RemoteControlBadge renders for Cursor sessions (because supportsRemoteControl = true).
  • Sidebar icon: pick something distinct from Claude/Codex (e.g. cursorarrow.rays or similar SF symbol; final pick goes to the implementer).

CLI changes

Three things to confirm empirically during implementation

These are the only gaps the doc fetch didn't close. Address inline during C-equivalent dev work, not as a separate sub-issue.

  1. Does agent -p (headless print mode) honor ~/.cursor/hooks.json? Docs detail hooks for Cmd+K + Agent Chat + Cloud Agents (reduced set), but headless mode is silent. Test: drop a beforeShellExecution hook that writes to a file, run agent -p with a prompt that triggers a shell call, see whether the hook fires. If it doesn't, fall back to --output-format stream-json as the signal source for headless runs — Cursor emits typed events (system, assistant, tool_call start/completed, result with duration_ms) on stdout. CursorSignalSource parses the JSONL stream as an alternate input path.
  2. Exit codes for the agent binary. Docs only state "0 = success". Run failure scenarios (bad API key, rate limit, network failure, timeout, permission denied) and document the real codes. Drives the .error transitions in CursorSignalSource.
  3. .cursorrules legacy file — does Cursor still read it, or is it dropped in current versions? Silent in docs. CursorScaffolder should not write one either way; just confirm Cursor doesn't surface a warning when only AGENTS.md is present.

Test plan (mirrors PR #197 Phase C)

  • swift test at repo root passes.
  • swift test in every sub-package including new CrowCursor (target ~20–25 tests).
  • Manual: launch app without agent installed — picker stays disabled with two agents; no ~/.cursor/ files written.
  • Manual: launch app with agent installed — picker enables and shows all three agents; ~/.cursor/hooks.json written at first launch.
  • Manual: create a Cursor session — terminal launches agent; sidebar icon shows the Cursor symbol; detail header shows "Agent: Cursor"; RemoteControlBadge renders.
  • Manual: submit a prompt to Cursor — sidebar transitions .idle → .working → .done on afterAgentResponse (or result event in stream-json fallback).
  • Manual: crow send to a Cursor session works (verifies supportsRemoteControl = true).
  • Manual: existing Claude session in parallel behaves identically — per-worktree .claude/settings.local.json, --continue, OTEL env unchanged.
  • Manual: Manager session still hardcoded to .claudeCode.
  • Manual: Cursor session reads Crow's .claude/skills/* slash commands (e.g. user can run /crow-create-ticket from inside a Cursor session). This verifies the Skills compat claim.
  • Manual: AGENTS.md written by CursorScaffolder doesn't clobber an existing one written by CodexScaffolder — preserves ## Known Issues / Corrections section.

Supersedes / closes

Effort

L. Mirrors Codex's PR #197 sizing (~2,500 lines) but with a real HookConfigWriter and SignalSource (closer to CrowClaude shapes than CrowCodex no-ops), partially offset by reusing AGENTS.md.template and the existing CLI flag plumbing.

🐦‍⬛ Created with Crow via Claude Code

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions