Risemi

How to install skills in Cursor

Cursor reads SKILL.md skills from .agents/skills and ~/.cursor/skills. How to install one, confirm it loaded and call it from Agent chat.

6 min read · Updated November 8, 2026 · Risemi team

Cursor supports agent skills: folders with a SKILL.md file that the Cursor agent loads when your request matches what the skill is for. Installing one takes a minute. You put the folder in a skills directory, make sure Cursor has picked it up, and then either let the agent use it on its own or call it with / in Agent chat.

If you are not sure what a skill is or how it differs from a Cursor rule, read what agent skills are first. The short version is below.

Skills vs Cursor rules

Cursor already has rules (.cursor/rules/*.mdc), so why skills? Rules are mostly about context that should be present when certain files are open or always. Skills are procedures: a test-first loop, a review checklist, a design process. The agent reads only each skill's name and description up front and loads the full instructions when the task calls for them.

The other advantage is portability. A skill written for Cursor uses the same SKILL.md format as Claude Code, Codex, GitHub Copilot and Gemini CLI, so you can share one folder across tools and teammates.

Step 1: Pick the folder

Cursor scans these locations:

ScopeFolderNotes
Project.agents/skills/<skill-name>/Shared format, also read by Codex, Gemini CLI and others
Project.cursor/skills/<skill-name>/Cursor-specific alternative
User~/.cursor/skills/<skill-name>/Available in every project
User~/.agents/skills/<skill-name>/Shared user folder, also read by Codex

For compatibility, Cursor also loads skills from .claude/skills/, .codex/skills/, ~/.claude/skills/ and ~/.codex/skills/. So if your repository already has Claude Code skills, Cursor sees them without any copying.

Risemi's installer uses .agents/skills/<slug> for projects and ~/.cursor/skills/<slug> for user-level installs. Our recommendation:

  • Use the project folder .agents/skills/ for skills that describe how this codebase works. Commit it.
  • Use ~/.cursor/skills/ for personal working methods you want in every project.
  • Do not put the same skill in several of these folders. Since Cursor reads many of them, you can end up with duplicates.

Cursor also scans nested project folders. A skill in packages/web/.agents/skills/ applies only to files within packages/web/, which is handy in a monorepo.

Step 2: Install the skill

Ask the Cursor agent to do it

Open Agent chat in your project and paste:

Read https://risemi.io/p/frontend-design-no-slop/install.md and follow it to install the "frontend-design-no-slop" skill into this agent.

Swap in the slug of the skill you want. The agent reads the product's install guide and writes the folder to the right place. Cursor will ask you to approve the file writes and any commands like any other agent action.

Use the CLI

Risemi's command-line installer is shipping this week:

npx risemi login
npx risemi add frontend-design-no-slop --agent cursor

Until it is on npm, use the agent line above or the ZIP.

Unzip it yourself

Each Risemi skill page has a ZIP. Unzip it into the skills folder:

mkdir -p .agents/skills
unzip frontend-design-no-slop.zip -d .agents/skills/

Then check the path. You want .agents/skills/frontend-design-no-slop/SKILL.md, with no extra folder level in between. The folder name must match the name field inside SKILL.md; Cursor's docs say name is required and must match the parent folder.

The same steps work for skills from GitHub or ones you write yourself.

Step 3: Make Cursor pick it up

Cursor discovers skills when it starts. If a skill you just added does not show up, reload the window (Command Palette, then "Developer: Reload Window") or restart Cursor. After that, edits to an existing skill's text are usually reflected in new chats, but reloading is the reliable fix when in doubt.

Step 4: Confirm it loaded

Two quick checks:

  1. In Cursor's settings, open the skills list. In recent versions it is under Customize, then Skills, where project and plugin skills appear in the "Agent Decides" section next to your rules. Menu names move between releases, so check your version's docs if you do not see it.
  2. In Agent chat, type / and start typing the skill name. If it appears in the menu, it is installed.

Step 5: Use it

Let the agent decide. Describe the task normally. If it matches the skill's description, the agent loads and follows the skill. With a design skill installed, "build a pricing section for this page" should trigger it.

Call it explicitly. Type / in Agent chat, pick the skill, and write your request. This attaches the skill to that one message. Use it when you want the skill for sure.

Some skills set disable-model-invocation: true in their frontmatter. Cursor supports this field: such a skill never loads on its own and only runs when you call it with /skill-name. That is common for skills that take an action, like deploying or opening a pull request.

Cursor also supports a paths field that limits automatic use to matching files (older skills may use globs, which still works as a fallback).

Scope: app/dashboard/page.tsx and the modules it imports (components/charts, UsageCard, ProjectList, RichTextEditor, lib/api.ts).

| # | Rule | File:line | Impact | Finding |
|---|---|---|---|---|
| 1 | async-dependencies | app/dashboard/page.tsx:8-10 | CRITICAL | getProjects(user.id) and getUsage(user.id) depend only on `user` but are awaited one after the other: 3 sequential round trips before any HTML is sent, where 2 would do. Promise.all them. |
| 2 | bundle-barrel-imports | components/ProjectList.tsx:3 | CRITICAL | `import _ from 'lodash'` in a 'use client' file ships all of lodash for one sortBy. Use lodash/sortBy, or toSorted / localeCompare. |
| 3 | async-suspense-boundaries | app/dashboard/page.tsx:7-19 | HIGH | The whole page, including the static shell and RichTextEditor, waits for all three fetches. Move usage and projects into async children in <Suspense> so the shell streams first. |
| 4 | server-serialization | app/dashboard/page.tsx:16 | HIGH | The raw projects array goes to client ProjectList, which reads only id, name and status. Map to {id, name, status} on the server. |
| 5 | rerender-no-inline-components | components/ProjectList.tsx:7 | HIGH | `Row` is defined inside ProjectList, so every keystroke in the filter remounts every <li>. Interaction cost, not first load. Hoist Row. |
| 6 | rerender-derived-state-no-effect | components/UsageCard.tsx:4-5 | MEDIUM | percent is kept in state and set in useEffect: server HTML renders "0% of plan used" and the real value appears only after hydration. Compute it during render. |

Score: 62 / 100 (100 − 2×10 CRITICAL − 3×5 HIGH − 1×3 MEDIUM). This counts rule findings; it is not a measured load time.

Checked and not scored: the local charts barrel (five tiny stubs, tree-shakeable); RichTextEditor is a <textarea> today, so no dynamic import yet. Not a performance rule but it blocks the build: components/charts/index.ts re-exports names the chart files do not export, and page.tsx reads `params.id` synchronously though params is a Promise in Next 15.

What makes the first load slow, in order:
1. The server waterfall (rows 1 and 3). Parallelizing projects and usage removes one full round trip; Suspense lets the header paint after only getUser.
2. Client JS weight (row 2). A one-line import change.
3. Payload and first-paint correctness (rows 4 and 6).
Row 5 is an interaction cost; fix it after the others. Fixes were not requested, so no files were changed.
SkillCodePro
  • Claude Code
  • Codex
  • Copilot
  • +2
Coding

React Performance Rules

70 prioritized performance rules for React and Next.js that your agent applies while coding and scores in review.

0 installs
$9.99

Moving old rules and commands to skills

If you have "Apply Intelligently" rules or custom slash commands, Cursor 2.4 added a built-in /migrate-to-skills that converts them. Dynamic rules without globs become normal skills; slash commands become skills with disable-model-invocation: true. Rules set to always apply, rules with glob patterns, and user rules are not converted. The generated skills land in .cursor/skills/ for you to review. Treat the output as a draft and read the descriptions, because they decide when each skill fires.

Troubleshooting

The skill does not show up

  • Check the exact path: <skills folder>/<name>/SKILL.md, with SKILL.md in capitals.
  • Look for a doubled folder from unzipping.
  • Make sure name in the frontmatter matches the folder name, in lowercase with hyphens.
  • Reload the window. Cursor scans at startup.

It shows up but never triggers automatically

Read the description. The agent decides from that text alone. A vague description ("Helps with frontend") gives the agent nothing to match. A good one says what the skill does and when to use it, in the words you actually type. Our guide on how to write a SKILL.md covers this. Also check that disable-model-invocation is not set to true.

It shows up twice

You probably have the same skill in two folders Cursor reads, for example .agents/skills/ and .claude/skills/. Keep one.

It triggers on everything

Narrow the description, add a paths pattern, or set disable-model-invocation: true and call it by hand.

Next steps

Browse skills that work in Cursor or the Cursor catalog. If your team mixes tools, see Claude Code vs Cursor skills, and the Claude Code install guide or Codex install guide.

FAQ

Where does Cursor look for skills?

In the project, `.agents/skills/` and `.cursor/skills/`, including nested subfolders. For your user, `~/.agents/skills/` and `~/.cursor/skills/`. For compatibility it also reads `.claude/skills/` and `.codex/skills/` plus their home-folder versions.

Do I need to restart Cursor after installing a skill?

Cursor discovers skills at startup, so a new skill may not appear until you reload the window or restart. It takes a few seconds and is the first thing to try when a skill is missing.

How do I run a skill manually in Cursor?

Type `/` in Agent chat, search for the skill name and select it. That attaches the skill to the message you send. Skills with `disable-model-invocation: true` only run this way.

Can I use Claude Code skills in Cursor?

Yes. Cursor reads `.claude/skills/` and `~/.claude/skills/` for compatibility, and the `SKILL.md` format is the same. Claude Code-only frontmatter fields that Cursor does not know are ignored.

What is the difference between a skill and a Cursor rule?

A rule is context that is attached always or when matching files are involved. A skill is a procedure the agent loads only when the task matches its description, and it can bundle scripts and reference files. Short project facts fit rules; longer step-by-step workflows fit skills.

Project: fresh `npm init -y` + `npm i -D vitest typescript` (Vitest 5.1.0, TypeScript strict). Every runner output below is pasted from the actual run.

Behaviour list (simplest first): 1. returns empty for blank input · 2. returns the address trimmed and lowercased when valid · 3. returns malformed when there is no @ · 4. returns malformed when there is more than one @ · 5. returns malformed when the domain has no dot · 6. returns too_long when the address is over 254 characters

Cycle 1: returns empty for blank input

```
[TDD] cycle 1/6  step RED  behaviour: returns empty for blank input
test:    src/validateEmail.test.ts > validateEmail > returns empty for blank input
ran:     npx vitest run src/validateEmail.test.ts -t "returns empty for blank input"
result:  FAIL  Error: Cannot find module './validateEmail' imported from <project>/src/validateEmail.test.ts
reason:  right (the unit under test does not exist yet: missing function, not a typo in the test)
changed: src/validateEmail.test.ts (+8, new file)
next:    minimum code in src/validateEmail.ts
```

```
[TDD] cycle 1/6  step GREEN  behaviour: returns empty for blank input
ran:     npx vitest run src/validateEmail.test.ts -t "returns empty for blank input" --reporter=verbose; npx vitest run src/validateEmail.test.ts
result:  PASS  Tests 1 passed (1)
changed: src/validateEmail.ts (+9, new file: result types + hard-coded `{ ok: false, error: 'empty' }`)
next:    refactor
```

Refactor: no refactor (hard-coded return is forced into the general version by cycle 2).

[Cycles 2–6 follow the same shape: each RED shows a real assertion mismatch such as `Tests 1 failed | 1 skipped (2)`, each GREEN the passing count, with refactors in cycles 5 and 6.]

Done

```
[TDD] DONE  cycles 6  tests added 6  suite PASS
ran:     npm test   (vitest run)   and   npx tsc -p .
result:  Test Files  1 passed (1) | Tests  6 passed (6); tsc exit 0
pre-existing failures: none (no tests existed)
found, not fixed: the 254 boundary itself (exactly 254 chars accepted) has no test; it passes today, so a test would be green on first run. 'a@.com' and 'a@b.' are accepted because the spec only asks for a dot in the domain.
```

No-fake-green check before every GREEN: no `.skip`/`.todo`, no assertion weakened, no test-only branch, nothing mocked.
SkillCodeFree
  • Antigravity
  • Claude Code
  • Codex
  • +3
Coding

Red-Green TDD Loop

Use it to make your coding agent build every change test-first: one failing test, the minimum code, a refactor, and a status block after each step.

0 installs
Free
Scope: app/dashboard/page.tsx and the modules it imports (components/charts, UsageCard, ProjectList, RichTextEditor, lib/api.ts).

| # | Rule | File:line | Impact | Finding |
|---|---|---|---|---|
| 1 | async-dependencies | app/dashboard/page.tsx:8-10 | CRITICAL | getProjects(user.id) and getUsage(user.id) depend only on `user` but are awaited one after the other: 3 sequential round trips before any HTML is sent, where 2 would do. Promise.all them. |
| 2 | bundle-barrel-imports | components/ProjectList.tsx:3 | CRITICAL | `import _ from 'lodash'` in a 'use client' file ships all of lodash for one sortBy. Use lodash/sortBy, or toSorted / localeCompare. |
| 3 | async-suspense-boundaries | app/dashboard/page.tsx:7-19 | HIGH | The whole page, including the static shell and RichTextEditor, waits for all three fetches. Move usage and projects into async children in <Suspense> so the shell streams first. |
| 4 | server-serialization | app/dashboard/page.tsx:16 | HIGH | The raw projects array goes to client ProjectList, which reads only id, name and status. Map to {id, name, status} on the server. |
| 5 | rerender-no-inline-components | components/ProjectList.tsx:7 | HIGH | `Row` is defined inside ProjectList, so every keystroke in the filter remounts every <li>. Interaction cost, not first load. Hoist Row. |
| 6 | rerender-derived-state-no-effect | components/UsageCard.tsx:4-5 | MEDIUM | percent is kept in state and set in useEffect: server HTML renders "0% of plan used" and the real value appears only after hydration. Compute it during render. |

Score: 62 / 100 (100 − 2×10 CRITICAL − 3×5 HIGH − 1×3 MEDIUM). This counts rule findings; it is not a measured load time.

Checked and not scored: the local charts barrel (five tiny stubs, tree-shakeable); RichTextEditor is a <textarea> today, so no dynamic import yet. Not a performance rule but it blocks the build: components/charts/index.ts re-exports names the chart files do not export, and page.tsx reads `params.id` synchronously though params is a Promise in Next 15.

What makes the first load slow, in order:
1. The server waterfall (rows 1 and 3). Parallelizing projects and usage removes one full round trip; Suspense lets the header paint after only getUser.
2. Client JS weight (row 2). A one-line import change.
3. Payload and first-paint correctness (rows 4 and 6).
Row 5 is an interaction cost; fix it after the others. Fixes were not requested, so no files were changed.
SkillCodePro
  • Claude Code
  • Codex
  • Copilot
  • +2
Coding

React Performance Rules

70 prioritized performance rules for React and Next.js that your agent applies while coding and scores in review.

0 installs
$9.99
Phase 1: Root cause investigation
Full suite: FAIL test_expired_coupon_rejected_twice (tests/test_checkout.py:16) AssertionError: Coupon(code='OLD50', …expires_at=2020-01-01…) is not None.
In isolation the same test fails at a different line (17): AssertionError: 50.0 != 100.0. So the first lookup in a process is rejected and every later lookup is accepted; in the full suite the first lookup was done by test_expired_coupon_rejected. Cross-test pollution through module-level state.
Cache probe at the boundary: cache before [] → 1st None → cache after ['OLD50'] → 2nd Coupon(OLD50). Docstring says the cache was added in 3f2a1c9 "to cut DB reads": that is the regression point.
Trace (root-cause-tracing.md): apply_coupon → get_coupon → `if code in _CACHE: return _CACHE[code]`. get_coupon writes the coupon to _CACHE before the expiry check, and the cache-hit branch has no expiry check at all.

Phase 2: Pattern analysis
Working path: cache miss → _load → expiry check. Broken path: cache hit. Expiry depends on `now`, passed per call, so it cannot be a cached property; "don't cache expired coupons" alone would still serve a coupon cached while valid after it expires.

Phase 3: Hypothesis
"get_coupon returns cached coupons without re-checking expires_at against the caller's now; the cache was added around the lookup but the validity check stayed only on the miss path." Confirmed by the probe above.

Phase 4: Implementation
Failing test first: test_cached_coupon_rechecked_for_expiry (look up while valid, then after expiry) plus a setUp that clears the module cache. Before the fix: 5 tests, 2 failures, both for the expected reason.
Fix at the source: the cache stores the raw record; the expiry check runs on every lookup, cached or not.

    c = _CACHE.get(code)
    if c is None:
        c = _load(code)
        if c is None:
            return None
        _CACHE[code] = c
    if c.expires_at < now:
        return None
    return c

Verification: python3 -m unittest -v → Ran 5 tests … OK. The originally failing test in isolation, 3 runs: OK, OK, OK. Fix attempts: 1.
SkillCodePro
  • Claude Code
  • Codex
  • Copilot
  • +2
Coding

Systematic Debugging Loop

Stops your coding agent from guessing: root cause first, one hypothesis at a time, failing test before the fix.

0 installs
$9.99

More guides