I Built a Cursor IDE Extension to Check My AI Requests Without Leaving the Editor

March 15, 2026 (1w ago)

Samyak Jain

Samyak Jain

Introduction

I use Cursor every day. And every day, at some point, I'd open a browser, navigate to the Cursor dashboard, and check how many fast requests I had left for the month.

It was a tiny thing. But it was annoying enough that I decided to fix it.

The result: a Cursor IDE extension that shows your remaining AI requests directly in the status bar — no browser, no dashboard, no context switching.

Cursor Usage Monitor

That's it. Always visible. Click to refresh. Turns red when you're running low. In this post I'll walk you through how it works, what I had to reverse engineer to build it, and the security decisions I made before publishing it.

The extension is live on the Open VSX registry: open-vsx.org/extension/Sammy970/cursor-usage

Table of Contents

The Problem

Cursor gives you 500 fast requests per month on the Pro plan. Fast requests are the ones that use the best models — GPT-4o, Claude Sonnet, etc. Once you hit the limit, you're bumped down to slow requests, which are rate-limited and noticeably laggy.

The only way to check your usage is to open cursor.com/settings in a browser. Every time I wanted to know where I stood, I had to leave the editor entirely. For something I check multiple times a day, it was a constant small friction.

What I wanted was simple: a number in the corner of my screen, always there, that I could glance at without breaking my flow.

Why the Obvious Approach Doesn't Work

My first instinct was to call the same API the Cursor dashboard uses:

GET https://cursor.com/api/usage?user=<USER_ID>

Simple enough. But this endpoint requires the browser session cookie WorkosCursorSessionToken — the one that gets set when you log into cursor.com in Chrome or Safari.

A VS Code extension runs in Node.js. It has no access to browser cookies. So even though I could find the right endpoint, I had no way to authenticate against it from inside the IDE.

I also tried making the request with credentials: "include" — that only works inside a browser context. In Node.js it's silently ignored. The server returns a 308 redirect to the non-www version and then a 401.

Back to square one.

Digging Into How Cursor Stores Auth

Cursor is built on Electron — which means it's essentially a Chromium browser wrapped around a Node.js runtime. Like VS Code, it stores all its persistent state in a local SQLite database:

PlatformPath
macOS~/Library/Application Support/Cursor/User/globalStorage/state.vscdb
Windows%APPDATA%\Cursor\User\globalStorage\state.vscdb
Linux~/.config/Cursor/User/globalStorage/state.vscdb

I opened this database and queried the ItemTable for anything auth-related:

SELECT key, value FROM ItemTable WHERE key LIKE 'cursorAuth%';

The results were exactly what I needed:

KeyValue
cursorAuth/accessTokeneyJhbGciOiJIUzI1NiIs... (JWT)
cursorAuth/refreshTokeneyJhbGciOiJIUzI1NiIs... (JWT)
cursorAuth/cachedEmailyou@example.com
cursorAuth/stripeMembershipTypepro

The accessToken is a JWT that the Cursor app itself uses to communicate with its own backend. I decoded the payload:

{
  "sub": "google-oauth2|user_01K1J3DH0GPY9ZT8J4ANY3Z1F5",
  "time": 1741234567,
  "exp": 1741238167,
  "iss": "https://cursor.com",
  "scope": "openid profile email",
  "aud": "https://api2.cursor.sh"
}

Notice the aud field: https://api2.cursor.sh. That's the audience this token is meant for — Cursor's actual backend API, not the public website. This was the lead I needed.

Finding the Right API Endpoint

With the token in hand, I started probing api2.cursor.sh. After a few attempts I hit the right endpoint:

GET https://api2.cursor.sh/auth/usage
Authorization: Bearer <accessToken>

Response:

{
  "gpt-4": {
    "numRequests": 150,
    "numRequestsTotal": 150,
    "numTokens": 132713206,
    "maxRequestUsage": 500,
    "maxTokenUsage": null
  },
  "startOfMonth": "2026-03-01T00:00:00.000Z"
}

No user ID parameter needed. No browser cookie. Just the Bearer token from the local database — and it works perfectly. Remaining requests = maxRequestUsage - numRequests.

Building the Extension

The extension is four TypeScript files:

src/
├── auth.ts       — reads the token from state.vscdb using sql.js
├── api.ts        — calls api2.cursor.sh/auth/usage
├── statusBar.ts  — renders the status bar item and tooltip
└── extension.ts  — activation, refresh command, 5-min auto-refresh

Reading the Token

I used sql.js — a pure WebAssembly build of SQLite — to read the database without any native compilation. This means it works on macOS, Windows, and Linux out of the box with no node-gyp build step.

export async function getAccessToken(): Promise<string> {
  const dbPath = getStateDbPath(); // platform-specific path
 
  const initSqlJs = require("sql.js");
  const SQL = await initSqlJs();
 
  const fileBuffer = fs.readFileSync(dbPath);
  const db = new SQL.Database(fileBuffer);
 
  const result = db.exec(
    `SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken' LIMIT 1`
  );
  db.close(); // closed immediately after read
 
  const token = result[0].values[0][0] as string;
  return token;
}

The database is opened read-only and closed immediately after the single query. The token is never stored anywhere by the extension itself.

Calling the API

The API call is made using Node's built-in https module — no third-party HTTP libraries:

export async function fetchUsage(token: string): Promise<UsageData> {
  const body = await httpsGet("https://api2.cursor.sh/auth/usage", token);
 
  const json = JSON.parse(body) as {
    "gpt-4"?: { numRequests?: number; maxRequestUsage?: number };
    startOfMonth?: string;
  };
 
  const model = json["gpt-4"] ?? {};
  const fastUsed = model.numRequests ?? 0;
  const fastLimit = model.maxRequestUsage ?? 500;
 
  return {
    fastRequestsUsed: fastUsed,
    fastRequestsLimit: fastLimit,
    fastRequestsRemaining: Math.max(0, fastLimit - fastUsed),
    startOfMonth: json.startOfMonth ?? "",
  };
}

The Status Bar Item

The status bar item lives in the bottom-right corner of Cursor. It shows the remaining count, turns warning-red below 50 requests, and displays a full breakdown on hover:

setUsage(data: UsageData, updatedAt: Date): void {
  const remaining = data.fastRequestsRemaining;
 
  this.item.text = `$(zap) ${remaining} left`;
 
  if (remaining < 50) {
    this.item.backgroundColor = new vscode.ThemeColor(
      "statusBarItem.warningBackground"
    );
  }
 
  this.item.tooltip = new vscode.MarkdownString([
    `**Cursor AI Usage**`,
    ``,
    `⚡ Fast requests used: ${data.fastRequestsUsed} / ${data.fastRequestsLimit}`,
    `✅ Remaining: **${remaining}**`,
    ``,
    `_Since ${new Date(data.startOfMonth).toLocaleDateString()}_`,
    `_Last updated: ${updatedAt.toLocaleTimeString()}_`,
    ``,
    `_Click to refresh_`,
  ].join("\n"));
}

Wiring it all Together

The extension.ts entry point handles activation, the refresh command, and the 5-minute auto-refresh timer:

export function activate(context: vscode.ExtensionContext): void {
  statusBarItem = new UsageStatusBarItem();
 
  const command = vscode.commands.registerCommand("cursorUsage.refresh", () => {
    refresh();
  });
 
  context.subscriptions.push(command);
 
  // Initial fetch on startup
  refresh();
 
  // Auto-refresh every 5 minutes
  refreshTimer = setInterval(() => refresh(), 5 * 60 * 1000);
}

Security Considerations

Before publishing to the Open VSX registry, I audited the extension carefully. Here's what I checked:

1. Token Handling

The access token is read from disk on each refresh call and used immediately. It is never written to disk by the extension, never logged to the output channel, and never stored in VS Code's settings or secret storage. Once the API call completes, it's out of scope and garbage collected.

2. Network Calls

The token is sent exclusively to https://api2.cursor.sh/auth/usage over HTTPS. There are no third-party servers, no telemetry endpoints, no analytics calls. You can verify this by reading the source — api2.cursor.sh is the only outbound URL in the entire codebase.

3. Error Message Sanitisation

Two specific error messages needed sanitising before publishing:

  • API errors — originally included the raw response body, which could theoretically echo back auth material. Fixed to surface only the HTTP status code.
  • Missing database errors — originally included the full filesystem path to state.vscdb, which would expose the user's home directory in the status bar tooltip. Fixed to show a generic message.

4. Read-Only Database Access

state.vscdb is opened with fs.readFileSync (read-only) and the database is closed immediately after extracting the single token value. The extension cannot modify Cursor's internal state.

How to Install It

The extension is published on the Open VSX registry, which is what Cursor uses for its Extensions panel.

  1. Open the Extensions panel (Cmd+Shift+X)
  2. Search for Cursor Usage
  3. Install the one by Sammy970

Or install it directly from your terminal:

cursor --install-extension Sammy970.cursor-usage

The source code is on GitHub: github.com/Sammy970/cursor-usage-extension

Conclusion

The most interesting part of this project wasn't the 200 lines of TypeScript — it was the investigation. Finding the right API by probing endpoints, reading JWTs from a local SQLite database, understanding what Cursor's Electron shell stores and where.

If you're building tools around Cursor or VS Code, the state.vscdb file is worth exploring. It stores everything from auth tokens to recently opened folders to per-extension state. A lot is possible if you know where to look.

The extension is open source under the MIT licence. If you use Cursor daily and find it useful, feel free to star the repo, open issues, or contribute improvements.