Files
claude-engineering-plugin/docs/plans/2026-02-12-feat-add-cursor-cli-target-provider-plan.md
Kieran Klaassen 0aaca5a7a7 Add Cursor CLI as target provider (#179)
* feat(cursor): add Cursor CLI as target provider

Add converter, writer, types, and tests for converting Claude Code
plugins to Cursor-compatible format (.mdc rules, commands, skills,
mcp.json). Agents become Agent Requested rules (alwaysApply: false),
commands are plain markdown, skills copy directly, MCP is 1:1 JSON.

* docs: add Cursor spec and update README with cursor target

* chore: bump CLI version to 0.5.0 for cursor target

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: note Cursor IDE + CLI compatibility in README

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:16:43 -06:00

12 KiB

title, type, date
title type date
Add Cursor CLI as a Target Provider feat 2026-02-12

Add Cursor CLI as a Target Provider

Overview

Add cursor as a fourth target provider in the converter CLI, alongside opencode, codex, and droid. This enables --to cursor for both convert and install commands, converting Claude Code plugins into Cursor-compatible format.

Cursor CLI (cursor-agent) launched in August 2025 and supports rules (.mdc), commands (.md), skills (SKILL.md standard), and MCP servers (.cursor/mcp.json). The mapping from Claude Code is straightforward because Cursor adopted the open SKILL.md standard and has a similar command format.

Component Mapping

Claude Code Cursor Equivalent Notes
agents/*.md .cursor/rules/*.mdc Agents become "Agent Requested" rules (alwaysApply: false, description set) so the AI activates them on demand rather than flooding context
commands/*.md .cursor/commands/*.md Plain markdown files; Cursor commands have no frontmatter support -- description becomes a markdown heading
skills/*/SKILL.md .cursor/skills/*/SKILL.md Identical standard -- copy directly
MCP servers .cursor/mcp.json Same JSON structure (mcpServers key), compatible format
hooks/ No equivalent Cursor has no hook system; emit console.warn and skip
.claude/ paths .cursor/ paths Content rewriting needed

Key Design Decisions

1. Agents use alwaysApply: false (Agent Requested mode)

With 29 agents, setting alwaysApply: true would flood every Cursor session's context. Instead, agents become "Agent Requested" rules: alwaysApply: false with a populated description field. Cursor's AI reads the description and activates the rule only when relevant -- matching how Claude Code agents are invoked on demand.

2. Commands are plain markdown (no frontmatter)

Cursor commands (.cursor/commands/*.md) are simple markdown files where the filename becomes the command name. Unlike Claude Code commands, they do not support YAML frontmatter. The converter emits the description as a leading markdown comment, then the command body.

3. Flattened command names with deduplication

Cursor uses flat command names (no namespaces). workflows:plan becomes plan. If two commands flatten to the same name, the uniqueName() pattern from the codex converter appends -2, -3, etc.

Rules (.mdc) Frontmatter Format

---
description: "What this rule does and when it applies"
globs: ""
alwaysApply: false
---
  • description (string): Used by the AI to decide relevance -- maps from agent description
  • globs (string): Comma-separated file patterns for auto-attachment -- leave empty for converted agents
  • alwaysApply (boolean): Set false for Agent Requested mode

MCP Servers (.cursor/mcp.json)

{
  "mcpServers": {
    "server-name": {
      "command": "npx",
      "args": ["-y", "package-name"],
      "env": { "KEY": "value" }
    }
  }
}

Supports both local (command-based) and remote (url-based) servers. Pass through headers for remote servers.

Acceptance Criteria

  • bun run src/index.ts convert --to cursor ./plugins/compound-engineering produces valid Cursor config
  • Agents convert to .cursor/rules/*.mdc with alwaysApply: false and populated description
  • Commands convert to .cursor/commands/*.md as plain markdown (no frontmatter)
  • Flattened command names that collide are deduplicated (plan, plan-2, etc.)
  • Skills copied to .cursor/skills/ (identical format)
  • MCP servers written to .cursor/mcp.json with backup of existing file
  • Content transformation rewrites .claude/ and ~/.claude/ paths to .cursor/ and ~/.cursor/
  • /workflows:plan transformed to /plan (flat command names)
  • Task agent-name(args) transformed to natural-language skill reference
  • Plugins with hooks emit console.warn about unsupported hooks
  • Writer does not double-nest .cursor/.cursor/ (follows droid writer pattern)
  • model and allowedTools fields silently dropped (no Cursor equivalent)
  • Converter and writer tests pass
  • Existing tests still pass (bun test)

Implementation

Phase 1: Types

Create src/types/cursor.ts

export type CursorRule = {
  name: string
  content: string  // Full .mdc file with YAML frontmatter
}

export type CursorCommand = {
  name: string
  content: string  // Plain markdown (no frontmatter)
}

export type CursorSkillDir = {
  name: string
  sourceDir: string
}

export type CursorBundle = {
  rules: CursorRule[]
  commands: CursorCommand[]
  skillDirs: CursorSkillDir[]
  mcpServers?: Record<string, {
    command?: string
    args?: string[]
    env?: Record<string, string>
    url?: string
    headers?: Record<string, string>
  }>
}

Phase 2: Converter

Create src/converters/claude-to-cursor.ts

Core functions:

  1. convertClaudeToCursor(plugin, options) -- main entry point

    • Convert each agent to a .mdc rule via convertAgentToRule()
    • Convert each command (including disable-model-invocation ones) via convertCommand()
    • Pass skills through as directory references
    • Convert MCP servers to JSON-compatible object
    • Emit console.warn if plugin.hooks has entries
  2. convertAgentToRule(agent, usedNames) -- agent -> .mdc rule

    • Frontmatter fields: description (from agent description), globs: "", alwaysApply: false
    • Body: agent body with content transformations applied
    • Prepend capabilities section if present
    • Deduplicate names via uniqueName()
    • Silently drop model field (no Cursor equivalent)
  3. convertCommand(command, usedNames) -- command -> plain .md

    • Flatten namespace: workflows:plan -> plan
    • Deduplicate flattened names via uniqueName()
    • Emit as plain markdown: description as <!-- description --> comment, then body
    • Include argument-hint as a ## Arguments section if present
    • Body: apply transformContentForCursor() transformations
    • Silently drop allowedTools (no Cursor equivalent)
  4. transformContentForCursor(body) -- content rewriting

    • .claude/ -> .cursor/ and ~/.claude/ -> ~/.cursor/
    • Task agent-name(args) -> Use the agent-name skill to: args (same as codex)
    • /workflows:command -> /command (flatten slash commands)
    • @agent-name references -> the agent-name rule (use codex's suffix-matching pattern)
    • Skip file paths (containing /) and common non-command patterns
  5. convertMcpServers(servers) -- MCP config

    • Map each ClaudeMcpServer entry to Cursor-compatible JSON
    • Pass through: command, args, env, url, headers
    • Drop type field (Cursor infers transport from command vs url)

Phase 3: Writer

Create src/targets/cursor.ts

Output structure:

.cursor/
├── rules/
│   ├── agent-name-1.mdc
│   └── agent-name-2.mdc
├── commands/
│   ├── command-1.md
│   └── command-2.md
├── skills/
│   └── skill-name/
│       └── SKILL.md
└── mcp.json

Core function: writeCursorBundle(outputRoot, bundle)

  • resolveCursorPaths(outputRoot) -- detect if path already ends in .cursor to avoid double-nesting (follow droid writer pattern at src/targets/droid.ts:31-50)
  • Write rules to rules/ as .mdc files
  • Write commands to commands/ as .md files
  • Copy skill directories to skills/ via copyDir()
  • Write mcp.json via writeJson() with backupFile() for existing files

Phase 4: Wire into CLI

Modify src/targets/index.ts

import { convertClaudeToCursor } from "../converters/claude-to-cursor"
import { writeCursorBundle } from "./cursor"
import type { CursorBundle } from "../types/cursor"

// Add to targets:
cursor: {
  name: "cursor",
  implemented: true,
  convert: convertClaudeToCursor as TargetHandler<CursorBundle>["convert"],
  write: writeCursorBundle as TargetHandler<CursorBundle>["write"],
},

Modify src/commands/convert.ts

  • Update --to description: "Target format (opencode | codex | droid | cursor)"
  • Add to resolveTargetOutputRoot: if (targetName === "cursor") return path.join(outputRoot, ".cursor")

Modify src/commands/install.ts

  • Same two changes as convert.ts

Phase 5: Tests

Create tests/cursor-converter.test.ts

Test cases (use inline ClaudePlugin fixtures, following codex converter test pattern):

  • Agent converts to rule with .mdc frontmatter (alwaysApply: false, description populated)
  • Agent with empty description gets default description text
  • Agent with capabilities prepended to body
  • Agent model field silently dropped
  • Agent with empty body gets default body text
  • Command converts with flattened name (workflows:plan -> plan)
  • Command name collision after flattening is deduplicated (plan, plan-2)
  • Command with disable-model-invocation is still included
  • Command allowedTools silently dropped
  • Command with argument-hint gets Arguments section
  • Skills pass through as directory references
  • MCP servers convert to JSON config (local and remote)
  • MCP headers pass through for remote servers
  • Content transformation: .claude/ paths -> .cursor/
  • Content transformation: ~/.claude/ paths -> ~/.cursor/
  • Content transformation: Task agent(args) -> natural language
  • Content transformation: slash commands flattened
  • Hooks present -> console.warn emitted
  • Plugin with zero agents produces empty rules array
  • Plugin with only skills works correctly

Create tests/cursor-writer.test.ts

Test cases (use temp directories, following droid writer test pattern):

  • Full bundle writes rules, commands, skills, mcp.json
  • Rules written as .mdc files in rules/ directory
  • Commands written as .md files in commands/ directory
  • Skills copied to skills/ directory
  • MCP config written as valid JSON mcp.json
  • Existing mcp.json is backed up before overwrite
  • Output root already ending in .cursor does NOT double-nest
  • Empty bundle (no rules, commands, skills, or MCP) produces no output

Phase 6: Documentation

Create docs/specs/cursor.md

Document the Cursor CLI spec as a reference, following docs/specs/codex.md pattern:

  • Rules format (.mdc with description, globs, alwaysApply frontmatter)
  • Commands format (plain markdown, no frontmatter)
  • Skills format (identical SKILL.md standard)
  • MCP server configuration (.cursor/mcp.json)
  • CLI permissions (.cursor/cli.json -- for reference, not converted)
  • Config file locations (project-level vs global)

Update README.md

Add cursor to the supported targets in the CLI usage section.

What We're NOT Doing

  • Not converting hooks (Cursor has no hook system -- warn and skip)
  • Not generating .cursor/cli.json permissions (user-specific, not plugin-scoped)
  • Not creating AGENTS.md (Cursor reads it natively, but not part of plugin conversion)
  • Not using globs field intelligently (would require analyzing agent content to guess file patterns)
  • Not adding sync support (follow-up task)
  • Not transforming content inside copied SKILL.md files (known limitation -- skills may reference .claude/ paths internally)
  • Not clearing old output before writing (matches existing target behavior -- re-runs accumulate)

Complexity Assessment

This is a medium change. The converter architecture is well-established with three existing targets, so this is mostly pattern-following. The key novelties are:

  1. The .mdc frontmatter format (different from all other targets)
  2. Agents map to "rules" rather than a direct equivalent
  3. Commands are plain markdown (no frontmatter) unlike other targets
  4. Name deduplication needed for flattened command namespaces

Skills being identical across platforms simplifies things significantly. MCP config is nearly 1:1.

References

  • Cursor Rules: .cursor/rules/*.mdc with description, globs, alwaysApply frontmatter
  • Cursor Commands: .cursor/commands/*.md (plain markdown, no frontmatter)
  • Cursor Skills: .cursor/skills/*/SKILL.md (open standard, identical to Claude Code)
  • Cursor MCP: .cursor/mcp.json with mcpServers key
  • Cursor CLI: cursor-agent command (launched August 2025)
  • Existing codex converter: src/converters/claude-to-codex.ts (has uniqueName() deduplication pattern)
  • Existing droid writer: src/targets/droid.ts (has double-nesting guard pattern)
  • Existing codex plan: docs/plans/2026-02-08-feat-convert-local-md-settings-for-opencode-codex-plan.md
  • Target provider checklist: AGENTS.md section "Adding a New Target Provider"