[luv-245] feat: add Cursor Agent CLI integration (beta)#245
Conversation
Update the README beta callout to clarify that GitHub Copilot CLI support is still under active testing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds Cursor Agent CLI integration across the failproofai hook system, project/session discovery, policy evaluation, and dashboard. Includes new Cursor-specific modules for session/project scanning, hook configuration support, event-name canonicalization, decision output formatting, registry entries, comprehensive test coverage, and documentation updates. Changes
Sequence DiagramsequenceDiagram
participant CursorCLI as Cursor CLI
participant Hook as Hook Runner
participant Handler as Event Handler
participant Evaluator as Policy Evaluator
participant Output as Decision Output
CursorCLI->>Hook: Invokes hook with camelCase event<br/>(preToolUse, postToolUse, etc.)
Hook->>Handler: Passes stdin payload to failproofai
Handler->>Handler: Canonicalizes camelCase to PascalCase<br/>(preToolUse → PreToolUse)
Handler->>Evaluator: Evaluates policy against<br/>canonical event type
Evaluator->>Evaluator: Determines decision<br/>(deny/instruct/allow)
alt Deny Decision
Evaluator->>Output: Build blocked message
Output->>CursorCLI: Return flat Cursor format<br/>{permission: "deny",<br/>user_message, agent_message}
else Instruct Decision
Evaluator->>Output: Build followup/context message
Output->>CursorCLI: Return Cursor format<br/>{followup_message} or<br/>{permission: "allow",<br/>additional_context}
else Allow Decision
Evaluator->>Output: Build allow response
Output->>CursorCLI: Return {permission: "allow",<br/>additional_context}
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 47 minutes and 51 seconds.Comment |
Mirrors the Codex (#220, #226, #232) and Copilot (#236) rollouts for Cursor Agent CLI. Hook install/uninstall, handler canonicalization, policy-evaluator output adapter, path-protection, activity dashboard filter + per-CLI badge, /projects merge, and session viewer fallback. Cursor's hook protocol differs from Claude/Codex/Copilot: - camelCase event keys (preToolUse, beforeSubmitPrompt, …) — handler canonicalizes via CURSOR_EVENT_MAP before policy lookup. - Flat array of hook entries per event (no Claude-style {hooks: [...]} matcher wrapper); writeHookEntries / removeHooksFromFile / hooksInstalledInSettings all walk the flat structure directly. - Stdout decision shape is {permission, user_message, agent_message, additional_context, followup_message} (not hookSpecificOutput). policy-evaluator.ts now branches on session.cli === "cursor" for the deny / instruct / allow-with-info paths and emits the Cursor shape. - No documented permission-mode equivalent; resolve-permission-mode.ts falls into the existing "default" branch (same as Copilot). - Detected via cursor-agent (preferred) or agent (legacy alias). Settings paths: user → ~/.cursor/hooks.json project → <cwd>/.cursor/hooks.json Path-protection (isAgentInternalPath + isAgentSettingsFile) extended to cover ~/.cursor/ and .cursor/hooks.json so the agent can't disable its own hooks. Frontend: lib/cli-registry.ts adds a "Cursor Agent" entry with an emerald badge; lib/projects.ts merges cursor projects; the project page (app/project/[name]/page.tsx) and session viewer (app/project/[name]/session/[sessionId]/page.tsx) extend the external-CLI fallback chain. The activity feed (hooks-client.tsx) recognizes /.cursor/ transcript paths. Adds lib/cursor-projects.ts + lib/cursor-sessions.ts as scaffold parsers — Cursor's transcript layout is undocumented, so the modules probe candidate subdirs (agent-sessions/, conversations/, sessions/) and metadata files (meta.json/session.json/workspace.{json,yaml}) and gracefully return [] when the directory layout doesn't match. The parser handles dotted-path record types (session.start, user.message, …) and degrades to system entries for unknown types so nothing is silently dropped — same shape as the Copilot parser. Drops the .cursor/hooks.json file at the project root (mirrors the existing .codex/hooks.json and .github/hooks/failproofai.json) so contributors developing failproofai with Cursor get hooks active automatically. Adjusts .gitignore to track that single file while leaving the rest of .cursor/ ignored. Tests: 60 unit suites / 1228 tests pass; 9 e2e suites / 235 tests pass (including the new __tests__/e2e/hooks/cursor-integration.e2e.test.ts which covers PreToolUse deny + agent-settings guard + beforeSubmitPrompt allow + activity tagging + install/uninstall flows). Bumps version to 0.0.10-beta.1. Also fixes a pre-existing parity gap: the project detail page (app/project/[name]/page.tsx) only listed Claude + Codex sessions, missing Copilot (and now Cursor); it now merges all four sources. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.cursor/hooks.json:
- Line 9: Remove the non-schema field "__failproofai_hook__" from all hook
objects in .cursor/hooks.json; Cursor only allows "type", "command", and
"timeout" per hook, so locate each hook entry containing the
"__failproofai_hook__" property and delete that key/value pair (preserving the
allowed fields like "type", "command", and "timeout") so the file conforms to
the hooks.json schema.
In `@CHANGELOG.md`:
- Around line 12-17: The changelog added a second "### Features" block under "##
Unreleased"; instead, merge the new Cursor Agent and Project page bullets into
the existing Unreleased subsections (fold the Cursor Agent CLI integration
details and the Project page bullet into the existing "### Features" and fold
README/logo note into "### Docs"), remove the duplicate "### Features" heading,
and keep references to the implemented symbols (CURSOR_EVENT_MAP,
isAgentInternalPath, isAgentSettingsFile, lib/cli-registry.ts, lib/projects.ts,
app/project/[name], /session/[id], and assets/logos/cursor-light.svg +
cursor-dark.svg) in-line within the appropriate subsection so the Unreleased
block remains a single coherent set of subsections.
In `@lib/cursor-projects.ts`:
- Around line 71-78: The JSON branch in parseCwdFromMetaText returns the raw
escaped string; instead try to JSON.parse the input text (inside a try/catch)
and read the cwd property so escape sequences (e.g. Windows backslashes) are
decoded, returning parsed.cwd if present; if JSON.parse throws or cwd is
missing, fall back to the existing regex checks (the YAML regex and the current
string-unquote step) so behavior remains unchanged for non-JSON metadata.
In `@lib/cursor-sessions.ts`:
- Around line 165-200: The user/assistant message branches in
lib/cursor-sessions.ts currently cast data.content/data.text with "as string"
and may push non-string values; update the user.message and assistant.message
handling (the lines that set `text = (data.content as string) ?? (data.text as
string) ?? ""` and the assistant ContentBlock creation) to first check typeof
data.content === "string" or typeof data.text === "string" and only use those
values when true, otherwise fall back to "" (and keep existing fallback to
pushing a GenericEntry when no text); ensure the produced entries (UserEntry,
AssistantEntry) always have string message content/ContentBlock.text to avoid
passing objects to the renderer while keeping baseEntry and rawCopy usage
intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: de718b89-4db5-4f7a-b48d-d35b627cb013
⛔ Files ignored due to path filters (2)
assets/logos/cursor-dark.svgis excluded by!**/*.svgassets/logos/cursor-light.svgis excluded by!**/*.svg
📒 Files selected for processing (35)
.cursor/hooks.json.gitignoreCHANGELOG.mdCLAUDE.mdREADME.md__tests__/components/project-list.test.tsx__tests__/e2e/helpers/hook-runner.ts__tests__/e2e/helpers/payloads.ts__tests__/e2e/hooks/cursor-integration.e2e.test.ts__tests__/hooks/handler.test.ts__tests__/hooks/install-prompt.test.ts__tests__/hooks/integrations.test.ts__tests__/lib/cli-registry.test.ts__tests__/lib/cursor-projects.test.ts__tests__/lib/cursor-sessions.test.ts__tests__/lib/projects.test.tsapp/policies/hooks-client.tsxapp/project/[name]/page.tsxapp/project/[name]/session/[sessionId]/page.tsxbin/failproofai.mjsdocs/configuration.mdxdocs/dashboard.mdxdocs/getting-started.mdxlib/cli-registry.tslib/cursor-projects.tslib/cursor-sessions.tslib/projects.tspackage.jsonsrc/hooks/builtin-policies.tssrc/hooks/handler.tssrc/hooks/install-prompt.tssrc/hooks/integrations.tssrc/hooks/policy-evaluator.tssrc/hooks/resolve-permission-mode.tssrc/hooks/types.ts
Five fixes from the bot review on PR #245: 1. .cursor/hooks.json: drop the `__failproofai_hook__` marker from the static dev config so the file matches Cursor's documented hook entry schema ({type, command, timeout}). The marker is still written by `cursor.buildHookEntry` for dynamically installed hooks; the legacy command-substring fallback in `isMarkedHook` identifies these static entries during uninstall. 2. CHANGELOG.md: fold the new Cursor entries into the existing Unreleased subsections (Features / Fixes / Docs) instead of creating a duplicate `### Features` heading. Resolves the MD024 markdownlint complaint. 3. lib/cursor-projects.ts: parse meta.json with `JSON.parse` first so escape sequences in JSON strings (Windows paths like `C:\\Users\\alice\\repo`) are decoded. Falls back to the YAML-ish regex only when the file isn't valid JSON. 4. lib/cursor-projects.ts: replace `findFirstExisting` (returns first existing path) with `findFirstUsableMeta` (returns first path whose parsed cwd is non-empty). Stops a stale `meta.json` from shadowing a valid `workspace.yaml` and dropping the session. 5. lib/cursor-sessions.ts: validate `data.content` / `data.text` with `typeof === "string"` before treating them as text in the user.message and assistant.message branches. Avoids surfacing non-string values via the `as string` casts. All 1228 unit + 235 e2e tests still pass. Lint clean (one pre-existing unrelated `<img>` warning). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Convention: each Unreleased bullet ends with `(#<PR>)`. Also extends the Features bullet to mention this repo dogfoods `.pi/settings.json`, matching the wording style used for the Cursor entry in #245. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion-bumps policy (#285) * [luv-cut-0.0.10-beta.0] chore: cut 0.0.10-beta.0 release Bumps package.json from 0.0.9-beta.3 to 0.0.10-beta.0 and rolls the ## Unreleased changelog section into ## 0.0.10-beta.0 — 2026-05-04. Why 0.0.10-beta.0 and not 0.0.9-beta.3: 0.0.9 is already published as `latest` on npm. Per semver, 0.0.9-beta.3 < 0.0.9 — publishing it would point the `beta` dist-tag at a version semver-older than the released 0.0.9, while shipping *more* features than 0.0.9 ever had. The next pre-release after a shipped 0.0.9 must live in the 0.0.10 line. Why the version had drifted to 0.0.13-beta.1 before #284 reset it: PRs #266 (OpenCode) and #267 (Pi) each speculatively bumped package.json in their feature branches even though no release was being cut. When unified into #270, the bumps stacked (0.0.10-beta.1 → .2 → 0.0.11-beta.1 → 0.0.12-beta.1 → 0.0.13-beta.1). Going forward, feature PRs should leave package.json alone — only release-cut PRs touch the version. Adds since v0.0.9: Features: - Add Gemini CLI integration (beta) (#277) - Add OpenCode (sst/opencode) integration (beta) (#270) - Add Pi (pi-coding-agent) integration (beta) (#270) - Add GitHub Copilot CLI integration (beta) (#236) - Add Cursor Agent CLI integration (beta) (#245) - Project page lists Copilot and Cursor sessions (#245) Fixes: - Pi integration: cache sessionId in shim (#284) - Cursor integration: support cursor-agent 2026-04+ layout (#283) - block-read-outside-cwd: deny message for all 6 CLIs (#270) - require-ci-green-before-stop: scope to current HEAD (#266) - failproofai policies --uninstall: correct selector wording (#236) - README: replace broken Copilot and Cursor logos (#236, #257) - Auto-translated MDX: sanitize JSX attribute quotes (#247) Docs: - README: drop "more coming soon" tagline (#281) - README: add Gemini, Pi, Cursor to supported-CLIs list (#277, #264, #245) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: add block-version-bumps custom policy Prevents the kind of drift that caused this very release. PRs #266 (OpenCode) and #267 (Pi) each speculatively bumped package.json in their feature branches, and when unified into #270 the bumps stacked all the way to 0.0.13-beta.1. PR #284 then over-corrected to 0.0.9-beta.3 — older than the already-published 0.0.9. The policy lives at .failproofai/policies/block-version-bumps.mjs (auto-loaded by failproofai's project-scope hooks). It blocks: - Edit/Write/MultiEdit on package.json that touches the "version" key - Bash: npm|yarn|pnpm|bun (pm) version <args> - Bash: sed|awk|jq mutating package.json referencing "version" Allowed when on a `luv-cut-*` branch — the established release-cut branch convention. Branch detection is a best-effort `git rev-parse` that fails open (returns false) so a missing/unusable git tree never blocks a legitimate edit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: address CodeRabbit review on block-version-bumps Three valid findings, all fixed: 1. sed/awk/jq detection (line 26): regex required `package.json` to appear before `version`, missing forms like `jq '.version="x"' package.json`. Switched to two non-consuming lookaheads so either ordering matches within a shell segment. 2. Value-only Edit/MultiEdit bypass (lines 74-84): an agent could issue `Edit { old_string: '"0.0.9-beta.3"', new_string: '"0.0.10-beta.0"' }` — neither string contains the literal `"version"` key, so the previous check let it through. Added STANDALONE_SEMVER_VALUE_RE plus an editTouchesVersion() helper that catches a value-only swap when both sides are bare semver-quoted strings that differ. The anchors (^ / $) and leading-digit requirement intentionally exclude range-prefixed dep entries (`"^1.2.3"`) and key-prefixed ones (`"react": "18.2.0"`), so dep-version Edits aren't false-positive. 3. Loose cut-branch match (line 36): `^luv-cut-/` allowed any suffix (e.g. `luv-cut-feature`). Tightened to require a semver-shape suffix: `^luv-cut-\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$`. Verified via 16 regex test cases (sed orderings, dep edits with keys, range prefixes, cut branch shapes). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…a.6 (#318) * [luv-319] fix: enforce Stop hook on Cursor Agent CLI (followup_message + SubagentStop parity) Cursor's `stop` hook ignores the flat `{permission: "deny"}` shape — that's honored on tool events only. The only force-retry channel for Stop is `{followup_message}` on stdout (exit 0), per https://cursor.com/docs/hooks. The instruct branch already used this shape correctly since #245; the deny path needed the same treatment, mirroring Copilot's #299 fix. Without this, the 5 require-*-before-stop builtins were observation-only on Cursor — the deny was logged but the agent stopped cleanly. User repro: session 1b510ad4-906c-4f30-9467-ff2e6c581cce at /home/nivedit/dev-purge. Also subscribes to `subagentStop` (CURSOR_HOOK_EVENT_TYPES + CURSOR_EVENT_MAP) and widens both deny and instruct branches to match it, for parity with the Copilot SubagentStop widening from #299. Cloud Agents caveat: Cursor Cloud Agent VMs do NOT run stop/subagentStop hooks at all, so this fix only covers local Cursor sessions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: cut 0.0.10-beta.6 release in CHANGELOG Promote the six entries accumulated under `## Unreleased` to a versioned heading `## 0.0.10-beta.6 — 2026-05-08`. Add a fresh `## Unreleased` heading at the top for the next development cycle. package.json was already at 0.0.10-beta.6 (pre-bumped); no version edit needed here. The CHANGELOG cut completes the release-prep handshake. Entries promoted: - Cursor Stop hook enforcement fix (this PR) - 5 scripts/translate-docs fixes from #305, #306, #307, #312, #313 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
/projectsmerge, session viewer fallback chain). Bumps version to 0.0.10-beta.1.preToolUse,beforeSubmitPrompt, …) — handler.ts canonicalizes viaCURSOR_EVENT_MAPso existing builtin policies fire unchanged.{hooks: [...]}matcher wrapper) —writeHookEntries/removeHooksFromFile/hooksInstalledInSettingswalk the flat structure.{permission, user_message, agent_message, additional_context, followup_message}) —policy-evaluator.tsbranches onsession.cli === "cursor"for deny / instruct / allow-with-info.defaultbranch.cursor-agent(preferred) oragent(legacy alias).~/.cursor/hooks.json(user) /<cwd>/.cursor/hooks.json(project).isAgentInternalPath+isAgentSettingsFile) extended to cover~/.cursor/and.cursor/hooks.json.lib/cli-registry.tsadds aCursor Agententry with an emerald badge;lib/projects.tsmerges Cursor projects; the project page and session viewer extend the external-CLI fallback chain. Activity feed recognizes/.cursor/transcript paths.lib/cursor-projects.ts+lib/cursor-sessions.tsas scaffold parsers — Cursor's transcript layout is undocumented, so the modules probe candidate subdirs (agent-sessions/,conversations/,sessions/) and metadata files (meta.json/session.json/workspace.{json,yaml}) and gracefully return[]when the layout doesn't match. Once the user reports the real layout the parser is one constant change away from a precise match..cursor/hooks.jsonat the project root so contributors using Cursor get hooks active automatically (mirrors the existing.codex/hooks.jsonand.github/hooks/failproofai.json)..gitignoreadjusted to track that single file.assets/logos/and the visual<picture>block inREADME.md.app/project/[name]/page.tsxpreviously listed only Claude + Codex sessions, missing Copilot (and now Cursor); it now merges all four sources.Test plan
bun run lint— clean (1 unrelated<img>warning)bun run test:run— 60 suites / 1228 tests pass (including new__tests__/lib/cursor-sessions.test.ts,__tests__/lib/cursor-projects.test.ts, extendedcli-registry.test.ts,projects.test.ts,project-list.test.tsx,integrations.test.ts,install-prompt.test.ts,handler.test.ts)bun run build— Next.js + dist/index.js build succeeds; type-check passesbun run test:e2e— 9 suites / 235 tests pass (including the new__tests__/e2e/hooks/cursor-integration.e2e.test.ts)failproofai policies --install --cli cursor --scope projectwrites.cursor/hooks.jsonwith 6 camelCase entries;--uninstallcleans itOpen follow-ups
[]gracefully; once we observe a real install, swap the candidate constants inlib/cursor-{sessions,projects}.tsfor precise matches.docs/i18n/README.*.md) anddocs/zh/,docs/pt-br/translations will pick up the Cursor mentions on the nextbun run translatecycle (same pattern as [luv-236] feat: add GitHub Copilot CLI integration (beta) #236 / [auto] update translations #233).🤖 Generated with Claude Code
Summary by CodeRabbit
--cli cursorflag