Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"version": 1,
"hooks": {
"sessionStart": [
{
"type": "command",
"command": "bun bin/failproofai.mjs --hook sessionStart --cli cursor",
"timeout": 60000
}
],
"sessionEnd": [
{
"type": "command",
"command": "bun bin/failproofai.mjs --hook sessionEnd --cli cursor",
"timeout": 60000
}
],
"beforeSubmitPrompt": [
{
"type": "command",
"command": "bun bin/failproofai.mjs --hook beforeSubmitPrompt --cli cursor",
"timeout": 60000
}
],
"preToolUse": [
{
"type": "command",
"command": "bun bin/failproofai.mjs --hook preToolUse --cli cursor",
"timeout": 60000
}
],
"postToolUse": [
{
"type": "command",
"command": "bun bin/failproofai.mjs --hook postToolUse --cli cursor",
"timeout": 60000
}
],
"stop": [
{
"type": "command",
"command": "bun bin/failproofai.mjs --hook stop --cli cursor",
"timeout": 60000
}
]
}
}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ next-env.d.ts
!.claude/settings.json

# cursor
.cursor
.cursor/*
!.cursor/hooks.json

# custom hooks loader temp files
*.__failproofai_tmp__.*
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

### Features
- Add GitHub Copilot CLI integration (beta) across hooks, activity dashboard, session fallback, and `/projects` listing. Also ships this repo's own `.github/hooks/failproofai.json` so contributors developing failproofai with the GitHub Copilot CLI get hooks active automatically, mirroring the existing `.claude/settings.json` and `.codex/hooks.json` (#236)
- Add Cursor Agent CLI integration (beta) across hooks, activity dashboard, session viewer, and `/projects` listing. New `--cli cursor` flag installs into `~/.cursor/hooks.json` (user) or `<cwd>/.cursor/hooks.json` (project) using Cursor's flat-array schema with camelCase event keys (`preToolUse`, `beforeSubmitPrompt`, …); the handler canonicalizes to PascalCase via `CURSOR_EVENT_MAP` so existing builtin policies fire unchanged. The policy evaluator emits Cursor's `{permission, user_message, agent_message, additional_context, followup_message}` stdout shape. Path-protection (`isAgentInternalPath` + `isAgentSettingsFile`) covers `~/.cursor/` and `.cursor/hooks.json`. Frontend: `lib/cli-registry.ts` adds a `Cursor Agent` entry with an emerald badge; `lib/projects.ts` merges Cursor projects into `/projects`; `app/project/[name]` and `/session/[id]` extend the external-CLI fallback chain. Also ships this repo's own `.cursor/hooks.json` so contributors using Cursor get hooks active automatically (#245).
- Project page (`/project/[name]`): list Copilot and Cursor sessions alongside Claude + Codex, mirroring the existing merge logic on the projects index. Previously the project detail view only enumerated Claude + Codex transcripts (#245).

### Fixes
- `failproofai policies --uninstall` interactive CLI selector now says "Remove Hooks" / "Choose where to remove from:" instead of "Install Hooks" / "Choose where to install:" (#236)
- README: replace the GitHub Copilot logo with the current canonical mark and add a dark-mode variant (`copilot-light.svg` + `copilot-dark.svg` via `<picture>`); the previous SVG used outdated path data with a hard-coded black fill that rendered invisibly on GitHub's dark theme (#236)

### Docs
- README: add Cursor Agent to the supported-CLIs intro line and visual list, with light/dark logo variants (`assets/logos/cursor-light.svg` + `cursor-dark.svg`). Note that GitHub Copilot CLI testing is ongoing in the beta callout (#245).

## 0.0.9 — 2026-04-28

### Features
Expand Down
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,34 @@ which writes a portable `npx -y failproofai --hook ... --cli copilot` command.
Same self-reference caveat applies — do **not** install the standard `npx`
form from inside this repo.

### Cursor hooks (`.cursor/hooks.json`)

This repo also ships a `.cursor/hooks.json` for Cursor Agent CLI sessions,
mirroring the `.claude/settings.json`, `.codex/hooks.json`, and
`.github/hooks/failproofai.json` setups. Cursor's hook config goes at the
project root under `.cursor/hooks.json` per the
[Cursor docs](https://cursor.com/docs/hooks). The schema is Cursor's flat
form: `version: 1`, camelCase event keys (`preToolUse`, `beforeSubmitPrompt`,
…), and a flat array of `{type, command, timeout}` entries per event (no
Claude-style `{hooks: [...]}` matcher wrapper). The handler canonicalizes
camelCase → PascalCase via `CURSOR_EVENT_MAP` before policy lookup so the
existing builtin policies fire unchanged.

Like Codex and Copilot, Cursor does not expose a `$CURSOR_PROJECT_DIR` env
var to the hook command line (only as a process env var inside the hook
itself), and Cursor hooks are spawned with the project root as cwd, so we
use a relative `bun bin/failproofai.mjs --hook ... --cli cursor` path. If
Cursor ever changes that behavior and the hook fails to find the binary,
switch to an absolute path.

For production users (outside this repo), the recommended Cursor install is:
```bash
failproofai policies --install --cli cursor --scope project
```
which writes a portable `npx -y failproofai --hook ... --cli cursor` command.
Same self-reference caveat applies — do **not** install the standard `npx`
form from inside this repo.

## Workflow rules

### One PR per branch
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

**Translations**: [简体中文](docs/i18n/README.zh.md) | [日本語](docs/i18n/README.ja.md) | [한국어](docs/i18n/README.ko.md) | [Español](docs/i18n/README.es.md) | [Português](docs/i18n/README.pt-br.md) | [Deutsch](docs/i18n/README.de.md) | [Français](docs/i18n/README.fr.md) | [Русский](docs/i18n/README.ru.md) | [हिन्दी](docs/i18n/README.hi.md) | [Türkçe](docs/i18n/README.tr.md) | [Tiếng Việt](docs/i18n/README.vi.md) | [Italiano](docs/i18n/README.it.md) | [العربية](docs/i18n/README.ar.md) | [עברית](docs/i18n/README.he.md)

The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code**, **OpenAI Codex**, **GitHub Copilot CLI** _(beta)_ & the **Agents SDK**.
The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code**, **OpenAI Codex**, **GitHub Copilot CLI** _(beta)_, **Cursor Agent** _(beta)_ & the **Agents SDK**.

<p align="center">
<img src="failproofai-hq.gif" alt="Failproof AI in action" width="800" />
Expand All @@ -44,10 +44,17 @@ The easiest way to manage policies that keep your AI agents reliable, on-task, a
</picture>
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://cursor.com/docs/hooks" title="Cursor Agent CLI">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/cursor-dark.svg" />
<img src="assets/logos/cursor-light.svg" alt="Cursor Agent" width="64" height="64" />
</picture>
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<strong>+ more coming soon</strong>
</p>

> Install hooks for one, two, or all three: `failproofai policies --install --cli copilot` (or `--cli claude codex copilot`). Omit `--cli` to auto-detect installed CLIs and prompt. **GitHub Copilot CLI support is in beta.**
> Install hooks for one or any combination: `failproofai policies --install --cli cursor` (or `--cli claude codex copilot cursor`). Omit `--cli` to auto-detect installed CLIs and prompt. **GitHub Copilot CLI and Cursor Agent support are in beta — testing is ongoing.**

- **39 Built-in Policies** - Catch common agent failure modes out of the box. Block destructive commands, prevent secret leakage, keep agents inside project boundaries, detect loops, and more.
- **Custom Policies** - Write your own reliability rules in JavaScript. Use the `allow`/`deny`/`instruct` API to enforce conventions, prevent drift, gate operations, or integrate with external systems.
Expand Down
34 changes: 34 additions & 0 deletions __tests__/components/project-list.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,39 @@ describe("ProjectList", () => {
expect(badgeNodes("GitHub Copilot")).toHaveLength(1);
});

it("renders a Cursor Agent badge for cli=['cursor']", () => {
const folders: ProjectFolder[] = [
{
name: "-home-u-cursor",
path: "/home/u/cursor",
isDirectory: true,
lastModified: new Date(),
lastModifiedFormatted: "Jun 15, 2024",
cli: ["cursor"],
},
];
render(<ProjectList folders={folders} />);
expect(badgeNodes("Cursor Agent")).toHaveLength(1);
});

it("renders all four badges when cli=['claude','codex','copilot','cursor']", () => {
const folders: ProjectFolder[] = [
{
name: "-home-u-quad",
path: "/home/u/quad",
isDirectory: true,
lastModified: new Date(),
lastModifiedFormatted: "Jun 15, 2024",
cli: ["claude", "codex", "copilot", "cursor"],
},
];
render(<ProjectList folders={folders} />);
expect(badgeNodes("Claude Code")).toHaveLength(1);
expect(badgeNodes("OpenAI Codex")).toHaveLength(1);
expect(badgeNodes("GitHub Copilot")).toHaveLength(1);
expect(badgeNodes("Cursor Agent")).toHaveLength(1);
});

it("links to /project/[name]", () => {
const folders = makeFolders(1);
render(<ProjectList folders={folders} />);
Expand Down Expand Up @@ -166,6 +199,7 @@ describe("ProjectList", () => {
"Claude Code",
"OpenAI Codex",
"GitHub Copilot",
"Cursor Agent",
]);
});

Expand Down
24 changes: 23 additions & 1 deletion __tests__/e2e/helpers/hook-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface HookRunResult {
export function runHook(
event: string,
payload: Record<string, unknown>,
opts?: { homeDir?: string; cli?: "claude" | "codex" | "copilot" },
opts?: { homeDir?: string; cli?: "claude" | "codex" | "copilot" | "cursor" },
): HookRunResult {
const binaryPath = getBinaryPath();

Expand Down Expand Up @@ -117,3 +117,25 @@ export function assertStopInstruct(result: HookRunResult): void {
expect(result.exitCode).toBe(2);
expect(result.stderr).toBeTruthy();
}

// ── Cursor-shaped assertions ───────────────────────────────────────────────
// Cursor uses a flat `{permission, user_message, agent_message}` JSON shape
// (no `hookSpecificOutput` wrapper) — see https://cursor.com/docs/hooks.

export function assertCursorDeny(result: HookRunResult): void {
expect(result.exitCode).toBe(0);
expect(result.parsed?.permission).toBe("deny");
expect(result.parsed?.user_message).toMatch(/Blocked/i);
expect(result.parsed?.agent_message).toMatch(/Blocked/i);
}

export function assertCursorInstruct(result: HookRunResult): void {
expect(result.exitCode).toBe(0);
expect(result.parsed?.permission).toBe("allow");
expect(result.parsed?.additional_context).toMatch(/^Instruction from failproofai:/);
}

export function assertCursorStopInstruct(result: HookRunResult): void {
expect(result.exitCode).toBe(0);
expect(result.parsed?.followup_message).toMatch(/^Instruction from failproofai:/);
}
74 changes: 74 additions & 0 deletions __tests__/e2e/helpers/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,80 @@ export const CodexPayloads = {
},
};

/**
* Cursor Agent CLI-accurate payload factories. Cursor delivers camelCase
* `hook_event_name` (`preToolUse`, `beforeSubmitPrompt`, …) plus snake_case
* fields (`tool_name`, `tool_input`, `cwd`). The failproofai handler
* canonicalizes camelCase → PascalCase via CURSOR_EVENT_MAP for internal
* lookup. Ref: https://cursor.com/docs/hooks (Stdin Payload Schema).
*/
const CURSOR_SESSION_ID = "test-session-cursor-001";

export const CursorPayloads = {
preToolUse: {
bash(command: string, cwd: string): Record<string, unknown> {
return {
session_id: CURSOR_SESSION_ID,
transcript_path: TRANSCRIPT_PATH,
cwd,
hook_event_name: "preToolUse",
tool_name: "Bash",
tool_input: { command },
};
},
write(filePath: string, content: string, cwd: string): Record<string, unknown> {
return {
session_id: CURSOR_SESSION_ID,
transcript_path: TRANSCRIPT_PATH,
cwd,
hook_event_name: "preToolUse",
tool_name: "Write",
tool_input: { file_path: filePath, content },
};
},
read(filePath: string, cwd: string): Record<string, unknown> {
return {
session_id: CURSOR_SESSION_ID,
transcript_path: TRANSCRIPT_PATH,
cwd,
hook_event_name: "preToolUse",
tool_name: "Read",
tool_input: { file_path: filePath },
};
},
},
postToolUse: {
bash(command: string, output: string, cwd: string): Record<string, unknown> {
return {
session_id: CURSOR_SESSION_ID,
transcript_path: TRANSCRIPT_PATH,
cwd,
hook_event_name: "postToolUse",
tool_name: "Bash",
tool_input: { command },
tool_output: output,
};
},
},
beforeSubmitPrompt(prompt: string, cwd: string): Record<string, unknown> {
return {
session_id: CURSOR_SESSION_ID,
transcript_path: TRANSCRIPT_PATH,
cwd,
hook_event_name: "beforeSubmitPrompt",
prompt,
};
},
stop(cwd: string): Record<string, unknown> {
return {
session_id: CURSOR_SESSION_ID,
transcript_path: TRANSCRIPT_PATH,
cwd,
hook_event_name: "stop",
};
},
};

/**
* Copilot CLI-accurate payload factories. We install Copilot hooks in
* "VS Code compatible" PascalCase mode, so Copilot delivers PascalCase
Expand Down
Loading