npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

effect-cursor-sdk

v0.3.0

Published

Effect-based wrapper around the Cursor SDK

Readme

effect-cursor-sdk

npm License MIT CI CodeRabbit Pull Request Reviews

Effect-native access to the new Cursor SDK.

effect-cursor-sdk wraps @cursor/sdk with Effect services, layers, scoped resource management, tagged errors, observability hooks, deterministic mocks, and ready-made runtimes. The upstream SDK remains the source of truth for Cursor-owned types; this package adds Effect ergonomics without creating a parallel model that can drift.

[!WARNING] This project is in early development. While all functionality is available, there is still much room for improvement. Contributions are welcome!

Philosophy

  • SDK-first: every public @cursor/sdk capability should be usable through this package.
  • Effect-native: APIs return Effect, Stream, Context.Service, and Layer values.
  • Type-preserving: SDK data types are re-exported instead of rebuilt.
  • Resource-safe: scoped helpers make it easy to dispose agents correctly.
  • Observable: SDK calls are wrapped in spans and metrics with secret redaction utilities.
  • Testable: mock layers and fixtures let applications test Cursor workflows without network calls.

Documentation

Feature Coverage

| SDK capability | Effect wrapper | | -------------------------------------------------------------------------------- | ---------------------------------------------- | | Agent.create, Agent.resume, Agent.prompt | CursorAgentService | | agent.send, reload, close, async dispose | CursorAgentService | | run.wait, stream, conversation, cancel, status listeners / streams, support checks | CursorRunService | | agent.listArtifacts, downloadArtifact | CursorArtifactService | | Agent.list, get, listRuns, getRun, messages | CursorInspectionService | | Agent.archive, unarchive, delete | CursorInspectionService | | Cursor.me, models, repositories | CursorInspectionService | | MCP servers, sub-agents, local/cloud options, model options | Defaults via CursorConfig / loadCursorConfig; merged SDK AgentOptions (deprecated at agent entry) | | Local run event helpers and platform helpers | Re-exported from @cursor/sdk |

Install

bun add effect-cursor-sdk effect @cursor/sdk

For development in this repo:

bun install
bun run typecheck
bun run test

Examples

The examples directory contains a guided learning path from a minimal first script to production-style Effect composition:

| Example | What it demonstrates | | --- | --- | | quickstart | First config-first local agent call with loadCursorConfig, agentOptionsFromConfig, and collectText. | | cli | A small terminal app with liveRuntime, offline makeMockRuntime, CLI overrides, and tagged error handling. | | basic-agent-workflow | Scoped agents, run status listeners, streaming, capability checks, and artifact listing/downloads. | | advanced-ops-dashboard | Inspection APIs, confirmation-gated lifecycle operations, parallel Effect composition, retries/timeouts, telemetry, redaction, and rich mocks. |

Run all example typechecks from the repo root:

bun run examples:typecheck

Quick Start

Load environment defaults with loadCursorConfig, then create agents with createFromConfig (and scopedFromConfig, promptFromConfig, resumeFromConfig as needed). The API key stays in Redacted form until the merge step; AgentOptions.apiKey remains a plain string at the SDK boundary.

import {
  CursorAgentService,
  CursorRunService,
  loadCursorConfig,
  liveLayer,
} from "effect-cursor-sdk";
import { Effect } from "effect";

const program = Effect.gen(function* () {
  const agents = yield* CursorAgentService;
  const runs = yield* CursorRunService;

  const config = yield* loadCursorConfig;
  const agent = yield* agents.createFromConfig(config, {
    // Override the given config optionally with custom values
    model: { id: "composer-2" },
    local: { cwd: process.cwd() },
  });

  const run = yield* agents.send(agent, "Explain this repository");
  const text = yield* runs.collectText(run);

  yield* agents.dispose(agent);
  return text;
}).pipe(Effect.provide(liveLayer));

Effect’s default ConfigProvider reads process.env, so you usually do not need to install a custom provider for this.

If you need full control over the merge into SDK options, you can still call agentOptionsFromConfig yourself and pass the result to deprecated create; prefer createFromConfig in application code.

Plain AgentOptions at the agent boundary (deprecated)

Passing raw AgentOptions (for example apiKey: process.env.CURSOR_API_KEY) to create, resume, prompt, or scoped is deprecated. It still works for compatibility, but prefer the config flow above.

import { CursorAgentService, CursorRunService, liveLayer } from "effect-cursor-sdk";
import { Effect } from "effect";

const legacyProgram = Effect.gen(function* () {
  const agents = yield* CursorAgentService;
  const runs = yield* CursorRunService;

  const agent = yield* agents.create({
    apiKey: process.env.CURSOR_API_KEY,
    model: { id: "composer-2" },
    local: { cwd: process.cwd() },
  });

  const run = yield* agents.send(agent, "Explain this repository");
  const text = yield* runs.collectText(run);
  yield* agents.dispose(agent);
  return text;
}).pipe(Effect.provide(liveLayer));

Migrate by replacing agents.create({ ... }) with const config = yield* loadCursorConfig and agents.createFromConfig(config, { ... }) (or the other *FromConfig helpers).

Scoped Agents

Prefer scopedFromConfig when an agent should be disposed automatically:

import { CursorAgentService, loadCursorConfig, liveLayer } from "effect-cursor-sdk";
import { Effect } from "effect";

const program = Effect.scoped(
  Effect.gen(function* () {
    const agents = yield* CursorAgentService;
    const config = yield* loadCursorConfig;
    const agent = yield* agents.scopedFromConfig(config, {
      model: { id: "composer-2" },
      local: { cwd: process.cwd() },
    });

    return yield* agents.send(agent, "Find risky code paths");
  }),
).pipe(Effect.provide(liveLayer));

Cloud Agents

Cloud options are merged as SDK overrides on top of loaded config:

const config = yield* loadCursorConfig;
const agent = yield* agents.createFromConfig(config, {
  model: { id: "composer-2" },
  cloud: {
    repos: [
      { url: "https://github.com/your-org/your-repo", startingRef: "main" },
    ],
    autoCreatePR: true,
  },
});

Streaming

CursorRunService.streamEvents preserves SDK event shapes and returns an Effect Stream.

import { Effect, Stream } from "effect";

const run = yield* agents.send(agent, "Refactor the auth module");

yield* runs.streamEvents(run).pipe(
  Stream.runForEach((event) => {
    if (event.type !== "assistant") {
      return Effect.void;
    }

    const text = event.message.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("");

    return Effect.sync(() => console.log(text));
  }),
);

Inspection And Metadata

Use CursorInspectionService for agent/run listings, messages, lifecycle operations, account metadata, model discovery, and connected repositories.

const inspection = yield* CursorInspectionService;

const agents = yield* inspection.listAgents({ runtime: "cloud", includeArchived: true });
const models = yield* inspection.listModels();
const repos = yield* inspection.listRepositories();

Integrate deeper with Effect

Because every Cursor call is an Effect, you compose it like the rest of your program: parallel requests, timeouts, retries, logging, and layers all work the same way.

This agent garden snapshot loads your catalog in parallel, adds a resilient boundary around the batch, logs a safe summary (counts and IDs only — never log API keys), then asks Cursor for a one-shot triage opinion via promptFromConfig:

import {
  CursorAgentService,
  CursorInspectionService,
  loadCursorConfig,
  liveLayer,
} from "effect-cursor-sdk";
import { Effect, Schedule } from "effect";

const agentGardenSnapshot = Effect.gen(function* () {
  const inspection = yield* CursorInspectionService;
  const agents = yield* CursorAgentService;
  const config = yield* loadCursorConfig;

  const catalog = yield* Effect.all(
    {
      cloud: inspection.listAgents({ runtime: "cloud", includeArchived: false }),
      models: inspection.listModels(),
      repos: inspection.listRepositories(),
    },
    { concurrency: "unbounded" },
  ).pipe(
    Effect.retry(
      Schedule.exponential("150 millis").pipe(Schedule.both(Schedule.recurs(3))),
    ),
    Effect.timeout("45 seconds"),
  );

  yield* Effect.logInfo("Cursor catalog loaded", {
    cloudAgents: catalog.cloud.items.length,
    models: catalog.models.length,
    repos: catalog.repos.length,
  });

  const triage = yield* agents.promptFromConfig(
    [
      "You are helping on-call. Here is non-secret inventory:",
      `- Cloud agents (ids): ${catalog.cloud.items.map((a) => a.agentId).join(", ") || "(none)"}`,
      `- Models (ids): ${catalog.models.map((m) => m.id).join(", ") || "(none)"}`,
      `- Repos (urls): ${catalog.repos.map((r) => r.url).join(", ") || "(none)"}`,
      "In two short sentences: what should we verify first before trusting automation here?",
    ].join("\n"),
    config,
    {
      model: { id: "composer-2" },
    },
  );

  return triage.result;
}).pipe(Effect.provide(liveLayer));

Swap liveLayer for mockLayer({ ... }) in tests and the same program shape exercises your orchestration without the network.

Errors

SDK failures are mapped into tagged errors such as CursorAuthenticationError, CursorRateLimitError, CursorConfigurationError, CursorNetworkError, and CursorUnsupportedOperationError. The original SDK error is preserved as cause, with safe operation context and retryability where available.

program.pipe(
  Effect.catchTag("CursorRateLimitError", (error) =>
    Effect.logWarning(`Cursor rate limited request: ${error.message}`),
  ),
);

Observability

Live service methods are wrapped with operation spans such as cursor.agent.create, cursor.run.wait, and cursor.artifacts.download. The package also exports metrics for operation starts, failures, and stream events, plus redact for safe metadata handling.

Never log API keys, MCP credentials, authorization headers, or prompt image data. The provided redaction helper treats those as sensitive by default.

[!WARNING] The redaction helper is a best-effort redactor for logs and attributes — not a cryptographic guarantee; do not rely on it for compliance redaction without review.

Mocks And Tests

Use mockLayer for deterministic tests:

import { CursorAgentService, loadCursorConfig, mockLayer } from "effect-cursor-sdk";
import { Effect } from "effect";

const testProgram = Effect.gen(function* () {
  const agents = yield* CursorAgentService;
  const config = yield* loadCursorConfig;
  const agent = yield* agents.createFromConfig(config, { model: { id: "composer-2" } });
  return yield* agents.send(agent, "Hello");
}).pipe(
  Effect.provide(
    mockLayer({
      result: { id: "run-1", status: "finished", result: "ok" },
    }),
  ),
);

API Surface

The main exports are:

  • Recipes — common compositions (prompt text, send + collect, pagination, lifecycle guards, artifacts) in RECIPES.md
  • Observability helpers (streamEventsTracked, collectTextTracked, catalog retry/timeout presets, log summaries)
  • CursorAgentService (prefer createFromConfig, scopedFromConfig, promptFromConfig, resumeFromConfig with loadCursorConfig; plain AgentOptions at the agent boundary is deprecated)
  • CursorRunService
  • CursorArtifactService
  • CursorInspectionService
  • CursorSdkFactory (deprecated for application code; low-level tests and overrides)
  • liveLayer, mockLayer, liveRuntime, makeMockRuntime
  • CursorConfig, cursorConfig, agentOptionsFromConfig, loadCursorConfig
  • tagged Cursor error classes and mapCursorError
  • SDK-owned types and utilities re-exported from @cursor/sdk

Use generated TypeScript declarations for exact signatures.

Quality Gates

bun run typecheck
bun run sdk-audit
bun run lint
bun run format:check
bun run test
bun run test:coverage
bun run build
bun run lint:package

After a @cursor/sdk bump, if sdk-audit fails, review docs/SDK_COVERAGE.md and refresh the baseline only when drift is intentional: bun run sdk-audit:refresh.

Coverage is measured with Vitest v8 coverage. The suite focuses on deterministic wrapper behavior; live SDK network paths should be validated separately with credentials and a disposable repository.

Deprecations

Whenever you need a single place for what is deprecated, what to use instead, how to migrate, and what may change in the next major (when that is already decided), read DEPRECATIONS.md. Pair it with CHANGELOG.md for release-by-release notes; @deprecated tags on exported symbols mirror the same intent for day-to-day coding.

Versioning and Publishing

Use conventional commits for readable history and changelog context:

feat: add cursor artifact helpers
fix: map cursor rate limit errors
docs: clarify runtime setup

User-facing changes should include a Changeset:

bun run changeset

On main, GitHub Actions uses Changesets to open a version PR when pending Changesets exist. After that PR is merged, the same workflow runs bun run release and publishes to NPM with the NPM_TOKEN repository secret.

For local release preparation, apply pending Changesets and publish only after the package is approved for public release:

bun run version
bun run release

bun run release runs verify:publish (including sdk-audit) before publishing to NPM.