# The complete guide to Cursor

> Learn Cursor from your first project to daily work: Agents Window, planning, visual edits, debugging, models, project rules, skills, MCP and the CLI.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-11-04 | Updated: 2026-11-04 | Topics: [AI](https://flaviocopes.com/tags/ai/) | Canonical: https://flaviocopes.com/cursor/

Cursor is a code editor with an AI agent that can work on your project. Describe a change, and the agent can find the relevant files, edit them, and run commands to check its work.

Its editor is based on VS Code, so you can open files and write code yourself. The **Agents Window** gives you another way to work, with a conversation beside your running application.

We'll use it to build a small game. As we change the game, we'll try the tools that help us direct the agent and review its code. Later we'll add project instructions and a reusable skill, look at external tool connections, and try the CLI.

The screenshots come from my AI Workshop walkthrough, recorded in May 2026, with some earlier CLI screenshots. They show the project we'll build. Some menus, models, and plans have changed since then; I'll point out the differences that affect these steps.

## What Cursor does

An AI model can generate code from a prompt. Cursor gives that model tools for working on your actual project.

Suppose a button doesn't work. The agent can find its event handler, read the surrounding code, and try a fix. It can then run the app or its tests to check the result, if those tools are available.

Read the changed code and click the button yourself. The agent's explanation can sound convincing even when the button is still broken.

```mermaid
flowchart TD
  accTitle: How a Cursor task becomes a code change
  accDescr: A request leads to reading project files, editing code, running checks, and a human review that can send the task back for another change.
  A[Describe the task] --> B[Read relevant files]
  B --> C[Edit the project]
  C --> D[Run checks and try the app]
  D --> E{Does it work?}
  E -->|No| B
  E -->|Yes| F[Review and keep the change]
```

You can do this from the editor, the Agents Window, or a terminal with the CLI. Cloud Agents work in a remote environment.

We'll keep the project and development server on your computer. Cursor still sends context to the model service for processing; the AI model isn't running offline on your machine.

If you're still deciding which kind of tool suits you, I wrote about the tradeoffs in [IDE, CLI, vibe coding tool, …how to decide?](https://flaviocopes.com/ide-cli-or-vibe-coding-tool/).

## Install Cursor and sign in

Download the installer for your system from [cursor.com/download](https://cursor.com/download).

![Cursor download page with installers for the supported operating systems](https://flaviocopes.com/images/cursor-guide/lesson-02-01.webp)

Run the installer and open Cursor. You can use its editor to browse files, but you'll need to sign in to use the account's AI features.

The welcome screen includes a Sign in button:

![Cursor welcome screen with the Sign in button highlighted](https://flaviocopes.com/images/cursor-guide/lesson-02-05.webp)

Follow the sign-in flow in your browser, then return to the app.

![Cursor account sign-in screen](https://flaviocopes.com/images/cursor-guide/lesson-02-06.webp)

If you're already familiar with VS Code, the editor view will look familiar. You have a file explorer, open files in tabs, an integrated terminal, and source control tools.

![The classic Cursor editor with a file explorer and empty editor area](https://flaviocopes.com/images/cursor-guide/lesson-02-08.webp)

For this tutorial, open the Agents Window. You can find **Open Agents Window** in the Command Palette. On macOS, open the palette with `Cmd+Shift+P`; on Windows and Linux, use `Ctrl+Shift+P`. To return later, search for **Open IDE**. Cursor's [Agents Window documentation](https://cursor.com/docs/agent/agents-window) explains both interfaces.

![The Agents Window with a conversation area and project navigation](https://flaviocopes.com/images/cursor-guide/lesson-02-09.webp)

For now, we need the conversation area, workspace picker, and the panel for files or the browser.

## Free usage, paid plans, and models

Check what your account includes before choosing a model. The screenshots show an older Ultra account, and the Pro pricing from that recording no longer applies to every subscriber.

Cursor has a free tier with limited AI usage. New paid subscriptions are part of the X subscription, which bundles Cursor, Grok, and X. Their included Cursor-model usage refreshes weekly and is shared with Grok. Older Cursor subscriptions keep their legacy billing arrangements. Use the [current pricing page](https://cursor.com/pricing) and your account dashboard to check which applies to you.

Check **included usage versus separately billed model usage**. On new subscriptions, third-party models need an eligible plan and prepaid credit. Seeing a model in the picker doesn't mean the monthly price covers its requests. Cursor's [models and pricing guide](https://cursor.com/docs/models-and-pricing) explains the pools and legacy plans.

Try a small real task before buying a higher tier. See how much usage it takes and how much of the result you keep.

The model reasons about the task and generates an answer. Cursor gives it access to your project and tools. Switching models can change the quality, speed, and cost of the same request.

| Choice | What to check |
| --- | --- |
| Your subscription | Included usage, reset period, and spending settings |
| The selected model | Whether your plan includes its usage |
| Higher effort or larger context | Whether the task needs the extra processing |
| Fast options | The price difference for the speed increase |
| Your own API key | Supported features, provider billing, and account eligibility |

Start with an empty example project so you can follow each file the agent creates.

## Create your first project

We'll build tic-tac-toe. Two players take turns choosing a square. The app shows whose turn it is, detects a winner or draw, and lets us start another game.

We can check it by playing. The board also gives us several bugs to look for, such as accepting a move after the game has ended.

In the Agents Window, open the workspace picker:

![The Open Workspace button highlighted in the Agents Window](https://flaviocopes.com/images/cursor-guide/lesson-05-02.webp)

Choose **Open Folder**:

![The workspace menu with Open Folder](https://flaviocopes.com/images/cursor-guide/lesson-05-03.webp)

Create an empty folder called `tic-tac-toe` somewhere you keep your projects, then open it. The folder is where Cursor will create the application files.

![Creating a new project folder in the folder picker](https://flaviocopes.com/images/cursor-guide/lesson-05-04.webp)

The file panel will be empty until we create the app.

You'll need Node.js, npm, and Git. Check them in a terminal:

```bash
node --version
npm --version
git --version
```

Use a current Node.js LTS release. Vite requires Node.js 20.19+ or 22.12+, and some templates require a newer version. Check the [Vite setup requirements](https://vite.dev/guide/#scaffolding-your-first-vite-project) if installation reports an unsupported engine. Fix the runtime before asking the agent to change application code.

The Workshop starts with this prompt:

```text
Create a sample Vite + React app featuring a simple tic-tac-toe game.
```

![The first prompt asking Cursor to create a Vite and React tic-tac-toe game](https://flaviocopes.com/images/cursor-guide/lesson-05-06.webp)

You can be more specific about the behavior without prescribing the implementation:

```text
Build a local two-player tic-tac-toe game with Vite and React.
Use JavaScript and npm. Keep everything in this project folder.
Show whose turn it is, prevent playing in occupied squares,
detect wins and draws, and add a New game button.
Keep the layout usable on a phone.
Use accessible buttons for the squares and a text status message.
Do not add accounts, a backend, or online multiplayer.
Run the production build and report the command and result.
```

Your generated code and design will differ from the screenshots. Use the requirements in the prompt to check your version.

Cursor starts creating files and running setup commands. Read commands that need approval. Installing the project's dependencies belongs to this task; changes to unrelated folders need an explanation.

![Cursor creating project files and setting up the application](https://flaviocopes.com/images/cursor-guide/lesson-05-07.webp)

## Run the app

Once the files are ready, ask:

```text
Run the app and open it in the built-in browser.
```

Cursor starts the development server and opens its local URL.

![The generated tic-tac-toe game running in Cursor's built-in browser](https://flaviocopes.com/images/cursor-guide/lesson-05-08.webp)

The development server lets you try the project on your computer. You still need a deployment step to publish it on the Internet.

If you need to start it yourself, inspect `package.json`. A Vite project normally has a `dev` script, which you run from the project folder:

```bash
npm run dev
```

Use the URL printed by the command. If another app is using the usual port, the server can choose a different one.

If the terminal says Vite is missing, check that you're in the folder containing `package.json`. For this npm project, run `npm install`, then retry `npm run dev`. Don't install Vite globally to compensate for missing project dependencies.

If the browser panel is blank, check that the server is still running and that the URL matches its output. Read any browser error before changing code.

```mermaid
flowchart TD
  accTitle: Find why the preview does not open
  accDescr: Check whether the development server is running, then check its actual URL, then investigate application errors.
  A[Read terminal output] --> B[Fix any startup error]
  B --> C[Keep the server running]
  C --> D[Open its printed URL]
  D --> E[Check browser errors]
  E --> F[Try the game]
```

## Use the right panel

Use the panel beside the conversation to inspect the app or its files while the agent works.

Open the file view to see what it created. You should be able to find `package.json`, a source directory, and the application's main files. Ask the agent to explain which file contains the game rules and which file contains the styles.

![The file panel showing the generated project files](https://flaviocopes.com/images/cursor-guide/lesson-06-01.webp)

Open a terminal when you need the output from a command. This is useful when an installation fails or a development server stops.

![Opening a terminal from the panel beside the conversation](https://flaviocopes.com/images/cursor-guide/lesson-06-03.webp)

The Git panel shows changes when the folder is a Git repository. An empty Git panel in a brand-new folder doesn't mean the agent created no files; the folder may not have a repository yet.

![Cursor's Git panel for a folder without a Git repository](https://flaviocopes.com/images/cursor-guide/lesson-06-04.webp)

You can hide the panel when you want more room for the conversation. For web work, I usually keep the browser open so I can see the effect of a change as I ask for it.

| Panel | Use it to answer |
| --- | --- |
| Files | Where did the agent put the code? |
| Browser | What does the application do? |
| Terminal | What happened when the command ran? |
| Git | What changed since the last saved version? |

When the agent says it started the server, check the terminal output. When it changes a color, look at the page.

## Check the game before changing the design

Number the squares from left to right, starting at the top:

```text
1 2 3
4 5 6
7 8 9
```

Start a new game before each row in this table. X goes first, then the players alternate. These are the square numbers to click, in order:

| Check | Click these squares | Expected result |
| --- | --- | --- |
| Horizontal X win | 1, 4, 2, 5, 3 | X wins after the fifth move |
| Diagonal X win | 1, 2, 5, 3, 9 | X wins after the fifth move |
| Vertical O win | 1, 2, 3, 5, 7, 8 | O wins after the sixth move |
| Draw | 1, 2, 3, 5, 4, 6, 8, 7, 9 | Full board, no winner |
| Occupied square | 1, 1, 2 | The second click is ignored; O occupies square 2 |

After a win, try clicking an empty square. The game should stay finished until you start a new one. Click New game and check that the board and turn indicator reset together.

Be this specific in your requests, too. “Stop accepting moves after a win” gives the agent a requirement it can check.

If a move is accepted after the game ends, report the exact steps:

```text
X wins across the top row. After the winning move,
I can still click an empty square and add another mark.
Stop accepting moves after a win or draw.
Keep New game working and check both cases.
```

You can describe that bug precisely without knowing which React state variable caused it.

When a fix is ready, repeat the steps. Also try the neighboring behavior, such as resetting after a draw. A change to end-of-game logic can affect both cases.

Save this working version before the next exercise. Inspect `.gitignore`: it should exclude `node_modules/` and `dist/`. Read `git status --short`, then stage only the game files, package files, and configuration you reviewed. Cursor's Source Control panel lets you select them individually. Commit with a message such as `Add working tic-tac-toe game`.

If the folder has no repository yet, run `git init` first. If Git asks for your name and email, configure your author identity before continuing. The [Git course](https://flaviocopes.com/courses/git/) covers that first setup.

If you want to understand the generated React code, the [free React course](https://flaviocopes.com/courses/react/) covers components, state, and events. That knowledge makes it easier to spot a fix that changes more than it needs to.

### Keep the game rules under test

The click sequences above give us a repeatable browser check. We can also test the game rules without a browser, which helps when a later feature breaks them.

Save the test file below before sending this request to Cursor. It asks for a small module we can check independently of the interface:

```text
Move the game rules into src/game-rules.mjs, with these exports:

createGame() returns { board: an array of nine null values, nextPlayer: 'X' }.
getWinner(board) returns 'X', 'O', or null.
playTurn(game, index) accepts a square index from 0 to 8.

A valid move returns a new game object and a copied board.
An occupied square, a won game, or a full board returns the original game.
An out-of-range or non-integer index throws RangeError.

Use these functions from the existing React interface.
Preserve its appearance and New game behavior. Add no dependencies.
Keep winner and draw messages separate from the next-player label.
Run the test file I provide below without changing its assertions.
```

Save this as `src/game-rules.test.mjs`. The test converts our human square numbers to JavaScript's zero-based indexes:

```js
import test from 'node:test'
import assert from 'node:assert/strict'
import { createGame, getWinner, playTurn } from './game-rules.mjs'

function play(squares) {
  return squares.reduce((game, square) => playTurn(game, square - 1), createGame())
}

for (const [name, squares, winner] of [
  ['horizontal X win', [1, 4, 2, 5, 3], 'X'],
  ['diagonal X win', [1, 2, 5, 3, 9], 'X'],
  ['vertical O win', [1, 2, 3, 5, 7, 8], 'O']
]) {
  test(name, () => {
    assert.equal(getWinner(play(squares).board), winner)
  })
}

test('an occupied square does not consume a turn', () => {
  const game = play([1])
  assert.equal(playTurn(game, 0), game)
  assert.equal(playTurn(game, 1).board[1], 'O')
})

test('a win stops further moves', () => {
  const game = play([1, 4, 2, 5, 3])
  assert.equal(playTurn(game, 8), game)
})

test('a draw has no winner and accepts no more moves', () => {
  const game = play([1, 2, 3, 5, 4, 6, 8, 7, 9])
  assert.equal(getWinner(game.board), null)
  assert.equal(game.board.includes(null), false)
  assert.equal(playTurn(game, 0), game)
})

test('a move leaves the previous board unchanged', () => {
  const original = createGame()
  const next = playTurn(original, 0)
  assert.equal(original.board[0], null)
  assert.equal(next.board[0], 'X')
  assert.equal(next.nextPlayer, 'O')
})

test('a new game has its own empty board and starts with X', () => {
  const first = createGame()
  const second = createGame()
  assert.notEqual(first.board, second.board)
  assert.deepEqual(second.board, Array(9).fill(null))
  assert.equal(second.nextPlayer, 'X')
})

test('invalid square indexes are rejected', () => {
  for (const index of [-1, 9, 1.5]) {
    assert.throws(() => playTurn(createGame(), index), RangeError)
  }
})
```

Run it from the project folder:

```bash
node --test src/game-rules.test.mjs
```

The `.mjs` extension lets Node run these JavaScript modules directly. No test package is needed. The expected moves came from the rules of the game. Review the test diff too. Changing an expected winner to match broken code would defeat the check.

If you get stuck comparing its implementation, this is a complete reference for `src/game-rules.mjs`:

```js
const winningLines = [
  [0, 1, 2], [3, 4, 5], [6, 7, 8],
  [0, 3, 6], [1, 4, 7], [2, 5, 8],
  [0, 4, 8], [2, 4, 6]
]

export function createGame() {
  return { board: Array(9).fill(null), nextPlayer: 'X' }
}

export function getWinner(board) {
  for (const [a, b, c] of winningLines) {
    if (board[a] !== null && board[a] === board[b] && board[a] === board[c]) {
      return board[a]
    }
  }
  return null
}

export function playTurn(game, index) {
  if (!Number.isInteger(index) || index < 0 || index > 8) {
    throw new RangeError('Square index must be an integer from 0 to 8')
  }

  if (getWinner(game.board) || !game.board.includes(null) || game.board[index] !== null) {
    return game
  }

  const board = [...game.board]
  board[index] = game.nextPlayer
  return { board, nextPlayer: game.nextPlayer === 'X' ? 'O' : 'X' }
}
```

The reference passes all nine tests. Try removing only `getWinner(game.board) ||` from the guard in `playTurn`, then run them again. The check for moves after a win should fail. Restore the guard afterward.

These functions assume game states created by this module. They don't validate arbitrary saved games or implement networked play. The tests cover the listed cases, not every possible board.

The React component still needs to call the functions. Ask Cursor to point to those imports, then repeat the browser checks. Tests for an unused helper can all pass while the visible game remains broken.

Later, when you add the scoreboard, keep these checks. Also check that rendering the same finished board again doesn't count a second win.

## Work in the code editor

The editor is useful when you want to make a small change yourself, then ask for help with a particular function. We'll add a helper for the game's turn label so you can try both autocomplete and a selected-code request.

If you haven't committed the working game yet, do that first so you have a version to compare with.

Open the Command Palette and run **Open IDE**. This opens the same workspace in Cursor's editor. Use `Cmd+Shift+P` on macOS or `Ctrl+Shift+P` on Windows and Linux. You can return with **Open Agents Window**. The [interface guide](https://cursor.com/docs/agent/agents-window) explains the switch.

Open the Explorer to see the files, or use `Cmd+P` on macOS (`Ctrl+P` elsewhere) to search for a filename. Before changing the generated app, find its main React component and read how it chooses the turn, winner, and draw messages.

### Try Tab suggestions

Create `src/turn-label.js`. Begin typing an exported function named `turnLabel` with a `player` parameter. Cursor's **Tab** feature can suggest the rest as gray text.

Accept a suggestion only when it matches the function you want. For this first version, the complete file should do this:

```js
export function turnLabel(player) {
  return `Next player: ${player}`
}
```

A suggestion may differ or may not appear. You can finish the file yourself; the result should return `Next player: X` for `turnLabel('X')` and `Next player: O` for `turnLabel('O')`.

| Action | Default shortcut |
| --- | --- |
| Accept the whole Tab suggestion | `Tab` |
| Reject it | `Escape`, or keep typing |
| Accept the next word on macOS | `Cmd+Right Arrow` |
| Accept the next word on Windows/Linux | `Ctrl+Right Arrow` |

Partial acceptance is useful when the beginning is right but the rest makes an assumption you don't want. If Tab offers another editing location afterward, inspect where it will take you before following it.

Use the Tab indicator in the editor's status bar to snooze suggestions or disable them for a file type. Custom keybindings can change these shortcuts; the [Tab guide](https://cursor.com/help/ai-features/tab) explains its controls.

### Send a selected function to Agent

Now we'll handle an invalid player value. This is a small edit you can understand without asking the agent to redesign the game.

Select the complete `turnLabel` function. Press `Cmd+L` on macOS or `Ctrl+L` on Windows and Linux to attach the selection to Agent. Current versions also support `Cmd+K` or `Ctrl+K` for this. Older tutorials call that shortcut **Inline Edit**; Cursor has replaced that interface with the selected-code Agent workflow. See [sending selected code to Agent](https://cursor.com/help/ai-features/agent#how-do-i-send-selected-code-to-agent).

Before submitting, check that the prompt references `src/turn-label.js` and the selected lines. Then ask:

```text
Change only the selected turnLabel function.
Keep the existing labels for X and O.
For every other value, return 'Choose X or O'.
Use no dependencies and leave the rest of the project alone.
```

Read the diff before keeping it. You should see a small input check, with the existing labels preserved. A complete implementation is:

```js
export function turnLabel(player) {
  if (player !== 'X' && player !== 'O') {
    return 'Choose X or O'
  }

  return `Next player: ${player}`
}
```

The agent's implementation may use different syntax. Check its behavior with Node from the project folder:

```bash
node --input-type=module -e "
import assert from 'node:assert/strict'
import { turnLabel } from './src/turn-label.js'
assert.equal(turnLabel('X'), 'Next player: X')
assert.equal(turnLabel('O'), 'Next player: O')
assert.equal(turnLabel(null), 'Choose X or O')
assert.equal(turnLabel('Z'), 'Choose X or O')
console.log('Turn labels passed')
"
```

This uses the module setup in a standard Vite React project. If your generated project uses a different module configuration, ask the agent to use its existing test setup instead.

Agent edits files as it works. Keep the hunks you want and reject unwanted ones in the diff view; if your version exposes different labels, use its review controls. Attaching a selection helps direct the request, but it doesn't enforce a boundary around those lines. Check the full project diff for extra changes.

Once the helper passes, ask Agent to use it for the game's ongoing-turn message. It should find the actual component and import path in your version. Keep the winner and draw messages unchanged, then play both outcomes again. A TypeScript project may need a typed helper or declaration; let the agent follow the project's existing conventions.

## Change the interface with Design Mode

It's easy to talk past the agent about a visual change. “The second blue thing below the heading” may point it at the wrong element.

Design Mode lets you point at the element inside the built-in browser and attach your request to it. In current versions, the toolbar calls this **Annotate**; earlier versions show a Design Mode icon. Cursor sends the selection and screenshot context along with your note. See the [Design Mode documentation](https://cursor.com/docs/agent/design-mode) for the current controls.

![The Design Mode icon highlighted above the running game](https://flaviocopes.com/images/cursor-guide/lesson-07-01.webp)

Activate it and select one of the game's marks. A note opens:

![Selecting an element in the tic-tac-toe game opens a prompt field](https://flaviocopes.com/images/cursor-guide/lesson-07-02.webp)

In the Workshop example, I ask to change its color to yellow:

![A note asking the agent to change the selected element to yellow](https://flaviocopes.com/images/cursor-guide/lesson-07-03.webp)

Cursor changes the code, and the running preview updates:

![The game after Cursor changes the marks to yellow](https://flaviocopes.com/images/cursor-guide/lesson-07-05.webp)

```mermaid
flowchart TD
  accTitle: A visual edit travels back to the source code
  accDescr: Select a running page element, attach a note, let the agent edit the source, and check the updated page.
  A[Select an element] --> B[Write the change]
  B --> C[Selection and screenshot context]
  C --> D[Agent edits source files]
  D --> E[Development preview updates]
  E --> F[Check the result]
```

Include the constraints when you request a visual change. Say whether a larger button should match the other buttons, or give the phone width where a heading wraps badly.

Try this next:

```text
Make the New game button easier to tap on a phone.
Keep the board inside the viewport at 375px width.
Preserve the current desktop layout.
```

Inspect both widths after the change. Selecting one element doesn't guarantee the agent only changes that element's styles; it might edit a shared class used elsewhere.

Check that the new color is readable against its background, including hover and disabled states when they exist.

## Ask, Plan, Agent, and Debug

Choose a mode before sending your request. Ask is useful for reading code; Plan lets you review an approach before Agent implements it.

Use **Ask** when you want to understand something. For example:

```text
Explain how this game detects a winner.
Point me to the relevant function and describe its inputs.
Don't change any files.
```

Use **Plan** when you want to settle an approach before implementation. Cursor can research the project, ask questions, and produce a plan you can review. `Shift+Tab` from the input switches to Plan Mode. The [planning documentation](https://cursor.com/docs/agent/plan-mode) explains how to save a plan in the workspace.

For the game, a good planning exercise is adding a scoreboard:

```text
Plan a scoreboard for X wins, O wins, and draws.
Keep the score between games while the page stays open.
Add a separate Reset scores action.
Explain where the state should live before implementing.
```

Check that New game preserves the scores. Look for anything you didn't request, such as storing them across page reloads or adding a dependency for state React already handles.

Once the approach matches what you want, ask Agent to implement it. For a tiny change, such as correcting a label, you can start directly in Agent.

**Debug** is useful when the problem needs investigation while the application runs. Give it a reproducible failure and follow the steps it asks you to perform. Debugging can involve adding temporary instrumentation and changing files, so don't treat it as a read-only mode.

![The task mode picker in the Workshop version of Cursor](https://flaviocopes.com/images/cursor-guide/lesson-08-01.webp)

The screenshot includes Multitask, which lets requests run concurrently in versions that expose it. For this exercise, finish one change before starting another. Otherwise, it becomes harder to tell which request changed the behavior you're testing.

| Need | Useful starting point |
| --- | --- |
| Understand existing code | Ask |
| Decide how a feature should work | Plan |
| Implement a clear change | Agent |
| Investigate a reproducible runtime problem | Debug |

The security settings still govern which commands and tool calls can run in each mode.

## Choose a model deliberately

Open the model picker beside the input. Depending on the account and version, you'll see automatic routing options and models you can select directly.

![The model picker with Auto enabled in the Workshop recording](https://flaviocopes.com/images/cursor-guide/lesson-09-01.webp)

The Workshop uses Composer for quick iterations and also shows the available models from other providers. Those names change, so pick from what your account offers now.

![The expanded model list in Cursor](https://flaviocopes.com/images/cursor-guide/lesson-09-02.webp)

For a small styling change, start with a model that's fast and economical for your plan. For a difficult bug, try a stronger reasoning model after giving it the reproduction and relevant files.

Before switching models after a poor answer, check the request. Include the expected behavior and error, and make sure the agent is working in the right folder.

The settings let you manage which models appear in the picker:

![Cursor model settings with visibility toggles for individual models](https://flaviocopes.com/images/cursor-guide/lesson-09-03.webp)

Some models expose effort, context, or speed settings. Those controls differ by model and account.

![Model options for context, reasoning effort, and speed](https://flaviocopes.com/images/cursor-guide/lesson-09-05.webp)

My advice is to change one setting at a time. Try fixing the same reproducible game bug in a saved copy. Keep the prompt unchanged while comparing models or effort levels.

A larger context window holds more material, but the agent still needs to find what matters. Start with a concise bug report and the relevant files.

## Give Cursor the context it needs

A new conversation needs enough information to find the work. Name the feature or file when you know it. Attach a screenshot for a visual issue. Paste the error text for a failed command.

For an existing application, begin with inspection:

```text
Read the README and package.json, then find the code for the game.
Explain how to run it and where the state is managed.
Don't change files yet.
```

Correct any wrong assumptions in the answer. If it describes a backend for our browser-only game, clear that up before asking for changes.

When a conversation grows into several unrelated tasks, start a new one with a short handoff. Include what changed, what still fails, and which checks already ran. Keep durable project decisions in files so the next conversation can read them.

After a failed attempt, describe what the current version does. Asking “try again” without new information can send the agent through the same failed approach.

### Attach the file you want it to read

Let's use the turn-label helper from the editor exercise. In the chat input, type `@turn-label.js` and choose `src/turn-label.js` from the file picker. Add the React component that displays the game's status too.

The selected file chips should appear with your prompt. Ask:

```text
Read the attached turn-label helper and the component that uses it.
Explain which message appears during a game, after a win, and after a draw.
Don't change files. Cite the relevant file and function for each answer.
```

Check those references against the editor. If the answer describes a function that isn't there, stop and confirm which files were attached. Attach a folder when the task spans that folder, rather than attaching the whole repository to every request. The [prompting guide](https://cursor.com/docs/agent/prompting#mentions) covers the available context types.

### Exclude private and irrelevant files

A game project might eventually contain local notes or service credentials. Decide what should stay out of AI context before adding those files.

Create `.cursorignore` in the project root. For example:

```gitignore
private-notes/
.env*
*.pem
```

If the file already exists, add the patterns you need to it. Don't replace someone else's exclusions with this example. Cursor uses Git-style ignore patterns and already excludes many generated and sensitive files by default; see the [ignore-file reference](https://cursor.com/docs/reference/ignore-file).

You can test the notes rule without using real private data. Create `private-notes/editor-check.txt` containing a harmless sentence, then try attaching it through the file picker. It should be excluded from that path into AI context. Remove the test file afterward if you don't need it.

The ignore file controls Cursor's file-context features. Terminal commands and MCP tools can still reach files through their own access. Keep secrets out of prompts and use operating-system or tool permissions when you need an enforced boundary. `.cursorignore` also doesn't prevent Git from committing a file; maintain `.gitignore` separately.

### Check Privacy Mode

Open **Cursor Settings → General** and enable **Privacy Mode**. Cursor describes this as preventing your code from being used for training by Cursor and its model providers.

Your prompts and relevant code still go to services for processing. If you use your own provider API key, check that provider's data policy too. Cursor's [privacy guide](https://cursor.com/help/security-and-privacy/privacy) explains the current controls and account differences.

## Write project rules

Rules tell Cursor how this repository works. Put the information you'd otherwise repeat every time in a short instruction file.

An `AGENTS.md` file at the project root is a straightforward starting point. For our game, it could contain:

```markdown
# Project instructions

This is a local two-player tic-tac-toe game built with Vite and React.

- Keep game rules separate from display styling.
- Use the existing package manager and scripts in package.json.
- Don't add dependencies for game logic or simple styling.
- Run `node --test src/game-rules.test.mjs` after changing the rules.
- Run `npm run build` before finishing.
- Also check wins, draws, occupied squares, and reset in the browser.
- Before finishing a UI change, check the game at 375px width.
- Explain what changed and which checks ran.
```

The dependency rule, for example, tells the agent to use the code already in the project when implementing game logic.

Cursor also supports project rules in `.cursor/rules/` using `.mdc` files, with options for applying them always, by file pattern, manually, or when relevant. Use those when different parts of a larger project need different instructions. The [rules documentation](https://cursor.com/docs/rules) describes their metadata and scope.

Don't put a whole handbook into an always-applied rule. A short description of the stack and a few project decisions is easier to maintain. Move a long procedure into a skill or a document the agent can read when it needs it.

Update rules when project commands change, so the next chat doesn't try to run a deleted test script.

## Make a reusable skill

A skill is a folder containing a `SKILL.md` file. It teaches the agent a particular workflow and can include supporting scripts or references.

I describe skills as recipes in the Workshop. Write down when to use one, the steps to follow, and how to check the result.

For example, create `.agents/skills/check-game/SKILL.md`:

```markdown
---
name: check-game
description: Check the tic-tac-toe game after changes to its rules or interface.
---

Read package.json and use the existing verification scripts.
Run node --test src/game-rules.test.mjs and npm run build.
Run the app and check a horizontal win, a diagonal win, and a draw.
Check that occupied squares and finished games reject new moves.
Start a new game and confirm the board and turn indicator reset.
Check the layout at 375px width.
Report any failed check and say when a check could not be performed.
```

Invoke a skill by typing `/` and choosing its name. That attaches it to one message; Cursor can also select a skill when its description matches the task. Project skills can live in `.agents/skills/` or `.cursor/skills/`, with personal skills under the corresponding home-directory paths.

Personal skills stay local unless you configure sharing. Only `~/.cursor/skills/` supports the optional Cloud Agents sync; `~/.agents/skills/` isn't copied to remote sessions. See the [skills documentation](https://cursor.com/docs/skills) for discovery, invocation, and sync.

Open a skill in the settings to inspect its instructions:

![Reading an installed skill's instructions inside Cursor](https://flaviocopes.com/images/cursor-guide/lesson-10-02.webp)

Our skill asks for a browser check. The agent needs browser tools to perform it; if they aren't available, it should report that step as unverified.

```mermaid
flowchart TD
  accTitle: Rules and skills provide different kinds of guidance
  accDescr: Project rules describe the repository while a game-checking skill supplies a task-specific procedure. Both guide the agent working on the game.
  A[Project rules] -->|Repository constraints| C[Agent task]
  B[Check-game skill] -->|Verification procedure| C
  C --> D[Run available tools]
  D --> E[Report results and missing checks]
```

Give each skill a small job. A game-checking skill has an obvious use after a game change. Keep deployment and billing procedures in separate skills.

## Connect external tools with MCP

MCP stands for **Model Context Protocol**. An MCP server exposes tools or data that an agent can use through a shared protocol.

A Notion connection might read pages, while a database connection might run queries. Check the tools a server exposes and the access you grant it.

The game doesn't need an external service, so you can finish it without adding an MCP server. Add a connection when you have a concrete task for it, such as reading a specification stored in Notion.

The Workshop shows the Customize marketplace:

![Cursor's Customize page with available integrations](https://flaviocopes.com/images/cursor-guide/lesson-11-01.webp)

You can install supported integrations there or open MCP settings from the Command Palette:

![The Command Palette with Open MCP Settings selected](https://flaviocopes.com/images/cursor-guide/lesson-11-03.webp)

For manual configuration, Cursor reads `.cursor/mcp.json` in the project and `~/.cursor/mcp.json` for user-level configuration. A local server runs as a process; a remote server is reached through a URL. Use the service's official configuration and authentication instructions. Cursor's [MCP documentation](https://cursor.com/docs/mcp) covers transports and configuration.

After connecting, confirm that Cursor can see the server and its tools:

![Cursor MCP settings showing configured servers and their status](https://flaviocopes.com/images/cursor-guide/lesson-11-04.webp)

Ask for a small read first, such as listing the pages available to the connection. Then ask it to read the specific specification you want to use.

```text
Read the game requirements from the connected project notes.
Compare them with the current app and list missing behavior.
Don't edit the notes or the code yet.
```

```mermaid
flowchart TD
  accTitle: Read an external specification through MCP
  accDescr: Cursor calls an MCP server to read a specification, compares the response with local code, and presents missing behavior before making changes.
  A[Request a comparison] --> B[Cursor calls a read tool]
  B --> C[MCP server checks access]
  C --> D[Specification returned]
  D --> E[Compare with local game]
  E --> F[Review proposed changes]
```

Name any external writes you want the agent to make. Updating a connected document changes something outside your repository, so you won't see the whole result in a local code diff.

A local MCP server can still send data to an external service. Check where the integration sends it before sharing sensitive material.

### Connect a public documentation server

We'll connect OpenAI's documentation server so you can try MCP without giving it access to a private account. It searches and reads public documentation; it doesn't call the OpenAI API for you. You can use it to look up how the Codex extension works inside Cursor.

This connection needs no API key or OAuth sign-in. It still sends your documentation query to the remote service, so use a general question rather than private project information. OpenAI publishes the server and client setup in its [Docs MCP guide](https://developers.openai.com/learn/docs-mcp).

Create `.cursor/mcp.json` in the game project's root:

```json
{
  "mcpServers": {
    "openaiDeveloperDocs": {
      "url": "https://developers.openai.com/mcp"
    }
  }
}
```

This is the complete file for a project with no other MCP entries. If yours already has servers, add `openaiDeveloperDocs` inside its existing `mcpServers` object. Keep the other entries.

Save the file, restart Cursor, and reopen the game workspace. In **Customize**, find the server and enable it if necessary. Wait until its tools appear before trying the query.

Now ask for a read:

```text
Use openaiDeveloperDocs to find the official instructions for using the
Codex IDE extension in Cursor. Read the most relevant documentation page,
then explain the installation and sign-in steps with a source link.
Don't install anything or change project files.
```

Open the tool-call details in the response. You should see documentation search and page retrieval, followed by an answer citing an official page. If the agent answers from memory without a tool call, ask it to use the connected server explicitly.

You don't need to install Codex for this exercise. The result we're checking is that Cursor can retrieve current information through a real external connection.

### Check a failed connection and remove it

To see a configuration failure in this disposable example, temporarily change the server URL to `https://developers.openai.com/`, the normal website homepage. Reconnect or restart Cursor. The homepage can load in a browser while still failing as an MCP endpoint. Inspect the server's status and connection error, then restore the exact `/mcp` URL and check that the tools return.

| What you see | What to check |
| --- | --- |
| Server never appears | JSON syntax, file location, and the open workspace |
| Connection or protocol error | Exact endpoint URL, network access, and server status |
| Server connects but no tool runs | Enabled tools, the request, and any approval waiting for you |
| An unexpected sign-in request | Confirm the server URL; this public example needs no credentials |

To stop using it, disable the server in Customize. To remove the project configuration, delete just the `openaiDeveloperDocs` entry from `.cursor/mcp.json`. If it was the only entry and you no longer need the file, remove that file. Restart or reconnect, then confirm the server is no longer available to a new request. Don't delete a shared configuration containing other connections.

## Control tool permissions

The agent can do more than edit source files. Depending on its tools, it can run commands, call external services, and fetch information from the network.

For local agents, Cursor's Run Modes are Auto-review, Allowlist, and Run Everything, under **Settings → Agents → Approvals & Execution**. Auto-review combines allowlisted calls, sandboxed shell commands where possible, and a classifier that reviews other calls. The classifier can make mistakes. Run Everything runs tool calls automatically, without the sandbox or classifier. See the [Run Modes guide](https://cursor.com/docs/agent/security/run-modes) before changing these settings. Cloud Agents run on their own machines and use different controls.

Our game needs access to its own files and development tools. Keep unrelated folders and accounts out of the task.

When an approval appears, read the operation and its target. `npm run build` in the game folder is easy to relate to the task. A command that deletes another directory isn't.

Keep credentials out of project instruction files and chat prompts. If a tool needs authentication, use its supported authentication mechanism. A secret committed to Git can remain in repository history even after you delete it from the current file.

## Review and save a change with Git

Commit each working version before asking for a larger feature, so the next diff starts from a known state.

If the new folder isn't a Git repository yet, initialize it from the project folder:

```bash
git init
```

Inspect the files before staging them. A typical Vite project ignores `node_modules/` and generated build output. Check `.gitignore` and make sure local secrets aren't among the files you're about to commit.

You can ask Cursor to explain the pending diff:

```text
Review the current diff.
Explain the behavior changes and call out unrelated edits.
Don't commit or push yet.
```

For a scoreboard feature, the relevant change should concern score state, display, and reset behavior. If the diff also replaces the styling system or adds a backend, ask why.

Run the project's checks and use the app. For a Vite project, a production build commonly runs with:

```bash
npm run build
```

After the build passes, play a draw and check that its counter increases. A build won't catch that behavior bug; you need to try it or run a browser test.

When you're happy, stage the relevant files and create a commit. The [free Git course](https://flaviocopes.com/courses/git/) explains staging, commits, branches, and recovery if those are new to you.

## Recover from an unwanted change

Let's undo a deliberate visual change so you know how recovery works before you need it for a larger mistake.

Start with a game that works. Read `git status --short` and save any work you want to keep. In a disposable tutorial project, make a commit containing the reviewed app before this exercise. If you have unrelated manual edits, commit them separately or copy them somewhere safe first.

Then give Agent a small, visible request:

```text
Change the New game button label to 'Start over'.
Keep its behavior, styles, and all other labels unchanged.
```

Try the result. Then pretend you preferred the old wording.

Stop any active agent before restoring files. Hover over the request and choose **Restore Checkpoint**, or open its checkpoint in the chat timeline. Preview which file state it will restore before confirming. Choose the checkpoint from before the label request.

Cursor creates checkpoints during Agent work and keeps them separately from Git. Restoring changes the files; it leaves the chat messages in place. The [checkpoint documentation](https://cursor.com/docs/agent/overview#checkpoints) describes the current controls.

After restoring, the button should say New game again. Run the app and check that it still resets a finished game. Read `git diff` as well, especially if you edited files manually after the checkpoint. A broad restore can remove changes you wanted to keep.

| What you want to do | Use |
| --- | --- |
| Reject one unwanted edit during review | That edit's diff controls |
| Return the Agent's files to an earlier session state | Preview and restore a checkpoint |
| Keep a reviewed version for future work or another machine | A Git commit |
| Recover only part of a file mixed with useful edits | Inspect the diff and make a targeted correction |

For example, if the label change and a useful accessibility fix share the same component, ask for a correction that preserves the useful work:

```text
Restore only the button text from 'Start over' to 'New game'.
Keep the accessibility change and my existing manual edits.
Show the resulting diff and don't commit yet.
```

Compare the resulting file with the work you saved before the exercise, so you can confirm your manual edits survived.

```mermaid
flowchart TD
  accTitle: Recover an unwanted game change
  accDescr: Stop active work, preserve useful edits, choose a targeted correction or a previewed checkpoint, and check the game and diff afterward.
  A[Stop active Agent] --> B[Save useful edits]
  B --> C{Keep part of the change?}
  C -->|Yes| D[Correct only the unwanted part]
  C -->|No| E[Preview earlier checkpoint]
  E --> F[Restore selected checkpoint]
  D --> G[Check game and Git diff]
  F --> G
```

Checkpoint recovery doesn't undo a message sent to an external service or a deployment. Check those systems separately. If the old conversation keeps pushing the wrong approach, start a new chat with a short account of what you restored and what should happen next.

## Use Cursor from the terminal

Cursor's CLI lets you work with its agent without opening the editor. It's useful if you already spend time in a terminal or want to run a focused review from a project directory.

The official installer for macOS, Linux, and WSL is:

```bash
curl https://cursor.com/install -fsS | bash
```

This runs the downloaded installer script. Use the command from [Cursor's CLI documentation](https://cursor.com/docs/cli/overview), especially if you need the Windows installation instructions.

![Installing the Cursor CLI from the terminal](https://flaviocopes.com/images/cursor-guide/lesson-12-01.webp)

Open a terminal in the game folder. Check the executable before starting a session:

```bash
agent --version
```

Another tool can own the same `agent` command. For example, Grok Build also uses that name. Cursor's installer also provides `cursor-agent`, which you can use explicitly:

```bash
cursor-agent --version
cursor-agent
```

If `agent --version` identifies Cursor, `agent` starts the same CLI. If neither command exists, reopen the terminal and follow the installer's PATH instructions. Don't continue in a different agent because its prompt looks familiar.

The first run asks you to sign in:

![The Cursor CLI sign-in prompt](https://flaviocopes.com/images/cursor-guide/lesson-12-02.webp)

It also asks whether you trust the folder. Check the displayed path before accepting. Starting an agent in your home directory gives it a very different starting point from starting it inside `tic-tac-toe`.

![The CLI workspace trust prompt showing the current project folder](https://flaviocopes.com/images/cursor-guide/lesson-12-03.webp)

Once you're in, describe the task as you would in the app:

```text
Review the game rules and identify missing edge cases.
Explain each issue with steps to reproduce it.
Don't edit files yet.
```

The Workshop shows the same workflow on an existing codebase, first asking for improvements and then reviewing the proposed changes:

![Asking the Cursor CLI to inspect an existing project for improvements](https://flaviocopes.com/images/cursor-guide/lesson-12-05.webp)

Read the findings before approving implementation. An agent may suggest cleanup that isn't necessary for the task you're doing.

![The Cursor CLI presenting issues it found in the project](https://flaviocopes.com/images/cursor-guide/lesson-12-06.webp)

Use `/` to discover commands available in your installed version:

![The Cursor CLI slash-command menu](https://flaviocopes.com/images/cursor-guide/lesson-12-08.webp)

The Workshop uses `/model` to choose a model, `/clear` to begin a fresh conversation, and `/open` to open the project in the IDE. Check the menu for the commands your version provides.

Use `@` to attach a file when you want the agent to inspect a particular part of the project:

![Attaching project files with the Cursor CLI context picker](https://flaviocopes.com/images/cursor-guide/lesson-12-09.webp)

The CLI also has shell access. I would keep the development server in a separate terminal so its output stays visible while the agent works.

## When a conversation goes wrong

Stop the agent if it keeps solving a different problem. Inspect its changes before sending another request.

Read the current changes. If they're useful, save them before trying another approach. If they aren't, use Git or the review interface to discard the specific changes you no longer want. Be careful when you already had your own uncommitted work in the same files.

Then start with a narrower request. For the game, “fix reset” is less useful than describing which state remains after pressing New game. Include the steps you tried and the result you expected.

| Problem | Next useful action |
| --- | --- |
| Agent edits the wrong feature | Stop and name the intended behavior and files |
| Preview is blank | Check the server URL and runtime errors |
| A fix causes another failure | Give both reproductions and inspect the shared logic |
| Model usage disappears quickly | Check the selected model and billing settings |
| Instructions keep being forgotten | Put stable project decisions in rules |
| A connection cannot read a document | Check the server status and document access |
| Conversation contains several unrelated tasks | Start a fresh chat with a short handoff |

Don't ask the agent to claim a check passed when it couldn't run it. “Build passed; browser behavior not checked” is a useful report because it tells you what remains to do.

## How I would use Cursor

For a web application, I would keep the running page beside the conversation and use small requests. I'd use Design Mode for a specific layout problem, then inspect the diff and check the page at a narrow width. Design Mode points at the built-in browser. For a native app outside that browser, I would hand the agent [agent-desktop](https://flaviocopes.com/agent-desktop/) and let it work from the accessibility tree.

For a change that affects several parts of an app, I would start with a plan. I'd want the agent to find the existing implementation and explain what it intends to reuse before creating new abstractions.

I would keep the project's stack, commands, and important constraints in rules. A repeated procedure, such as checking a release, would become a skill once I knew which steps were useful.

I don't like how easy it is to start more work than I can review. Each parallel agent leaves me another change to check. For my own projects, I'd usually finish one task before opening another.

For scripts or server maintenance, I'd use a terminal agent when I don't need the editor or visual tools. My [Codex guide](https://flaviocopes.com/codex/) walks through that alternative with a separate JavaScript project.

Try adding the scoreboard we planned earlier. Keep scores between games, give Reset scores its own action, and check that a draw increments only the draw counter.
