effect-cursor-sdk 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ # Deprecations
2
+
3
+ This document lists deprecated `effect-cursor-sdk` APIs, what to use instead today, and how names are expected to change in the **next major** release. Deprecated APIs remain available until that major; follow [CHANGELOG.md](./CHANGELOG.md) and release notes when upgrading.
4
+
5
+ ## Status definitions
6
+
7
+ | Status | Meaning |
8
+ | ---------------------- | ---------------------------------------------------------------------------------------------------- |
9
+ | **Deprecated** | Still supported; avoid in new code. May show IDE warnings via TSDoc `@deprecated`. |
10
+ | **Preferred now** | Current recommended API; use with [`loadCursorConfig`](./README.md#quick-start) and related helpers. |
11
+ | **Planned next major** | Intended replacement names after deprecated overloads and `*FromConfig` suffixes are removed. |
12
+
13
+ ## Agent entry: plain `AgentOptions` vs config-first
14
+
15
+ Passing raw [`AgentOptions`](https://cursor.com/docs/sdk/typescript) (including a plain `apiKey` string) directly to `CursorAgentService` agent entry points is **deprecated**. Prefer loading `CursorConfig` with `loadCursorConfig` (exported from this package) and using the `*FromConfig` methods so secrets stay [`Redacted`](https://effect.website/docs/schema/redacted/) until merged for the SDK boundary.
16
+
17
+ ### API mapping
18
+
19
+ | Deprecated (`CursorAgentService`) | Preferred now | Planned next major (same signatures as “Preferred now”) |
20
+ | --------------------------------- | ----------------------------------------------- | ------------------------------------------------------- |
21
+ | `create(options)` | `createFromConfig(config, overrides?)` | `create(config, overrides?)` |
22
+ | `resume(agentId, options?)` | `resumeFromConfig(agentId, config, overrides?)` | `resume(agentId, config, overrides?)` |
23
+ | `prompt(message, options?)` | `promptFromConfig(message, config, overrides?)` | `prompt(message, config, overrides?)` |
24
+ | `scoped(options)` | `scopedFromConfig(config, overrides?)` | `scoped(config, overrides?)` |
25
+
26
+ The `*FromConfig` suffixes exist today to keep deprecated plain-`AgentOptions` entry points without breaking callers. After removal of the legacy forms, the shorter names above are the intended stable surface.
27
+
28
+ ### Migrate `create`
29
+
30
+ **Before (deprecated):**
31
+
32
+ ```ts
33
+ const agent =
34
+ yield *
35
+ agents.create({
36
+ apiKey: process.env.CURSOR_API_KEY,
37
+ model: { id: "composer-2" },
38
+ local: { cwd: process.cwd() },
39
+ });
40
+ ```
41
+
42
+ **After (preferred):**
43
+
44
+ ```ts
45
+ const config = yield * loadCursorConfig;
46
+ const agent =
47
+ yield *
48
+ agents.createFromConfig(config, {
49
+ model: { id: "composer-2" },
50
+ local: { cwd: process.cwd() },
51
+ });
52
+ ```
53
+
54
+ If you need full control over merging into SDK options, call `agentOptionsFromConfig(config, overrides)` and pass the result only through internal or transitional code paths; application code should still prefer `createFromConfig`.
55
+
56
+ ### Migrate `prompt`
57
+
58
+ **Before (deprecated):**
59
+
60
+ ```ts
61
+ const result =
62
+ yield *
63
+ agents.prompt("Summarize the README", {
64
+ apiKey: process.env.CURSOR_API_KEY,
65
+ model: { id: "composer-2" },
66
+ });
67
+ ```
68
+
69
+ **After (preferred):**
70
+
71
+ ```ts
72
+ const config = yield * loadCursorConfig;
73
+ const result =
74
+ yield *
75
+ agents.promptFromConfig("Summarize the README", config, {
76
+ model: { id: "composer-2" },
77
+ });
78
+ ```
79
+
80
+ ## Other deprecations
81
+
82
+ `CursorSdkFactory` exposes raw SDK-style `create` / `resume` / `prompt` helpers for tests and advanced wiring; those are **deprecated for application code** in favor of `CursorAgentService` with the config-first flow above. See TSDoc on `CursorSdkFactory` in the published types.
83
+
84
+ ## Where else this is documented
85
+
86
+ - [README.md](./README.md) — quick summary and link here.
87
+ - Package root ships this file next to `README.md` on npm (see `package.json` `files`).
88
+ - Per-symbol `@deprecated` tags on `CursorAgentService` and related exports in the TypeScript declarations.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # effect-cursor-sdk
2
2
 
3
+ ![npm](https://img.shields.io/npm/v/effect-cursor-sdk) ![License MIT](https://img.shields.io/github/license/benjamin-kraatz/effect-cursor-sdk) ![CI](https://img.shields.io/github/actions/workflow/status/benjamin-kraatz/effect-cursor-sdk/ci.yml) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/benjamin-kraatz/effect-cursor-sdk?utm_source=oss&utm_medium=github&utm_campaign=benjamin-kraatz%2Feffect-cursor-sdk&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)
4
+
3
5
  Effect-native access to the new [Cursor SDK](https://cursor.com/docs/sdk/typescript).
4
6
 
5
7
  `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.
@@ -27,7 +29,7 @@ Effect-native access to the new [Cursor SDK](https://cursor.com/docs/sdk/typescr
27
29
  | `Agent.list`, `get`, `listRuns`, `getRun`, messages | `CursorInspectionService` |
28
30
  | `Agent.archive`, `unarchive`, `delete` | `CursorInspectionService` |
29
31
  | `Cursor.me`, models, repositories | `CursorInspectionService` |
30
- | MCP servers, sub-agents, local/cloud options, model options | SDK-owned `AgentOptions` and re-exported types |
32
+ | MCP servers, sub-agents, local/cloud options, model options | Defaults via `CursorConfig` / `loadCursorConfig`; merged SDK `AgentOptions` ([deprecated](./DEPRECATIONS.md) at agent entry) |
31
33
  | Local run event helpers and platform helpers | Re-exported from `@cursor/sdk` |
32
34
 
33
35
  ## Install
@@ -44,18 +46,44 @@ bun run typecheck
44
46
  bun run test
45
47
  ```
46
48
 
49
+ ## Examples
50
+
51
+ The [`examples`](./examples) directory contains a guided learning path from a
52
+ minimal first script to production-style Effect composition:
53
+
54
+ | Example | What it demonstrates |
55
+ | --- | --- |
56
+ | [`quickstart`](./examples/quickstart) | First config-first local agent call with `loadCursorConfig`, `agentOptionsFromConfig`, and `collectText`. |
57
+ | [`cli`](./examples/cli) | A small terminal app with `liveRuntime`, offline `makeMockRuntime`, CLI overrides, and tagged error handling. |
58
+ | [`basic-agent-workflow`](./examples/basic-agent-workflow) | Scoped agents, run status listeners, streaming, capability checks, and artifact listing/downloads. |
59
+ | [`advanced-ops-dashboard`](./examples/advanced-ops-dashboard) | Inspection APIs, confirmation-gated lifecycle operations, parallel Effect composition, retries/timeouts, telemetry, redaction, and rich mocks. |
60
+
61
+ Run all example typechecks from the repo root:
62
+
63
+ ```bash
64
+ bun run examples:typecheck
65
+ ```
66
+
47
67
  ## Quick Start
48
68
 
69
+ 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.
70
+
49
71
  ```ts
50
- import { CursorAgentService, CursorRunService, liveLayer } from "effect-cursor-sdk";
51
- import { Effect, Stream } from "effect";
72
+ import {
73
+ CursorAgentService,
74
+ CursorRunService,
75
+ loadCursorConfig,
76
+ liveLayer,
77
+ } from "effect-cursor-sdk";
78
+ import { Effect } from "effect";
52
79
 
53
80
  const program = Effect.gen(function* () {
54
81
  const agents = yield* CursorAgentService;
55
82
  const runs = yield* CursorRunService;
56
83
 
57
- const agent = yield* agents.create({
58
- apiKey: process.env.CURSOR_API_KEY,
84
+ const config = yield* loadCursorConfig;
85
+ const agent = yield* agents.createFromConfig(config, {
86
+ // Override the given config optionally with custom values
59
87
  model: { id: "composer-2" },
60
88
  local: { cwd: process.cwd() },
61
89
  });
@@ -68,56 +96,50 @@ const program = Effect.gen(function* () {
68
96
  }).pipe(Effect.provide(liveLayer));
69
97
  ```
70
98
 
71
- `AgentOptions.apiKey` is a plain string at the SDK boundary. To keep the key in `Redacted` form until that boundary, read `CURSOR_API_KEY` (and optional `CURSOR_MODEL`, `CURSOR_LOCAL_CWD`) with `loadCursorConfig`, then merge into `AgentOptions` with `agentOptionsFromConfig`.
72
- In a future version, we might drop the plain `AgentOptions` boundary and require all options to be passed through `CursorConfig` / `loadCursorConfig` / `agentOptionsFromConfig`.
73
-
74
99
  Effect’s default `ConfigProvider` reads `process.env`, so you usually do not need to install a custom provider for this.
75
100
 
76
- ### Quick start with `loadCursorConfig`
101
+ 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.
102
+
103
+ ## Plain `AgentOptions` at the agent boundary (deprecated)
104
+
105
+ 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.
77
106
 
78
107
  ```ts
79
- import {
80
- CursorAgentService,
81
- CursorRunService,
82
- agentOptionsFromConfig,
83
- loadCursorConfig,
84
- liveLayer,
85
- } from "effect-cursor-sdk";
108
+ import { CursorAgentService, CursorRunService, liveLayer } from "effect-cursor-sdk";
86
109
  import { Effect } from "effect";
87
110
 
88
- const program = Effect.gen(function* () {
111
+ const legacyProgram = Effect.gen(function* () {
89
112
  const agents = yield* CursorAgentService;
90
113
  const runs = yield* CursorRunService;
91
114
 
92
- const config = yield* loadCursorConfig;
93
- const agent = yield* agents.create(
94
- // Flexible: use the config from the environment, and override with local options.
95
- agentOptionsFromConfig(config, {
96
- model: { id: "composer-2" },
97
- local: { cwd: process.cwd() },
98
- }),
99
- );
115
+ const agent = yield* agents.create({
116
+ apiKey: process.env.CURSOR_API_KEY,
117
+ model: { id: "composer-2" },
118
+ local: { cwd: process.cwd() },
119
+ });
100
120
 
101
121
  const run = yield* agents.send(agent, "Explain this repository");
102
122
  const text = yield* runs.collectText(run);
103
-
104
123
  yield* agents.dispose(agent);
105
124
  return text;
106
125
  }).pipe(Effect.provide(liveLayer));
107
126
  ```
108
127
 
128
+ Migrate by replacing `agents.create({ ... })` with `const config = yield* loadCursorConfig` and `agents.createFromConfig(config, { ... })` (or the other `*FromConfig` helpers).
129
+
109
130
  ## Scoped Agents
110
131
 
111
- Prefer `scoped` when an agent should be disposed automatically:
132
+ Prefer `scopedFromConfig` when an agent should be disposed automatically:
112
133
 
113
134
  ```ts
114
- import { CursorAgentService, liveLayer } from "effect-cursor-sdk";
135
+ import { CursorAgentService, loadCursorConfig, liveLayer } from "effect-cursor-sdk";
115
136
  import { Effect } from "effect";
116
137
 
117
138
  const program = Effect.scoped(
118
139
  Effect.gen(function* () {
119
140
  const agents = yield* CursorAgentService;
120
- const agent = yield* agents.scoped({
141
+ const config = yield* loadCursorConfig;
142
+ const agent = yield* agents.scopedFromConfig(config, {
121
143
  model: { id: "composer-2" },
122
144
  local: { cwd: process.cwd() },
123
145
  });
@@ -129,19 +151,19 @@ const program = Effect.scoped(
129
151
 
130
152
  ## Cloud Agents
131
153
 
132
- Cloud options are passed through as SDK `AgentOptions`:
154
+ Cloud options are merged as SDK overrides on top of loaded config:
133
155
 
134
156
  ```ts
135
- const agent =
136
- yield *
137
- agents.create({
138
- apiKey: process.env.CURSOR_API_KEY,
139
- model: { id: "composer-2" },
140
- cloud: {
141
- repos: [{ url: "https://github.com/your-org/your-repo", startingRef: "main" }],
142
- autoCreatePR: true,
143
- },
144
- });
157
+ const config = yield* loadCursorConfig;
158
+ const agent = yield* agents.createFromConfig(config, {
159
+ model: { id: "composer-2" },
160
+ cloud: {
161
+ repos: [
162
+ { url: "https://github.com/your-org/your-repo", startingRef: "main" },
163
+ ],
164
+ autoCreatePR: true,
165
+ },
166
+ });
145
167
  ```
146
168
 
147
169
  ## Streaming
@@ -149,18 +171,24 @@ const agent =
149
171
  `CursorRunService.streamEvents` preserves SDK event shapes and returns an Effect `Stream`.
150
172
 
151
173
  ```ts
152
- const run = yield * agents.send(agent, "Refactor the auth module");
153
-
154
- yield *
155
- runs
156
- .streamEvents(run)
157
- .pipe(
158
- Stream.runForEach((event) =>
159
- event.type === "assistant"
160
- ? Effect.sync(() => console.log(event.message.content))
161
- : Effect.void,
162
- ),
163
- );
174
+ import { Effect, Stream } from "effect";
175
+
176
+ const run = yield* agents.send(agent, "Refactor the auth module");
177
+
178
+ yield* runs.streamEvents(run).pipe(
179
+ Stream.runForEach((event) => {
180
+ if (event.type !== "assistant") {
181
+ return Effect.void;
182
+ }
183
+
184
+ const text = event.message.content
185
+ .filter((block) => block.type === "text")
186
+ .map((block) => block.text)
187
+ .join("");
188
+
189
+ return Effect.sync(() => console.log(text));
190
+ }),
191
+ );
164
192
  ```
165
193
 
166
194
  ## Inspection And Metadata
@@ -168,13 +196,73 @@ yield *
168
196
  Use `CursorInspectionService` for agent/run listings, messages, lifecycle operations, account metadata, model discovery, and connected repositories.
169
197
 
170
198
  ```ts
171
- const inspection = yield * CursorInspectionService;
199
+ const inspection = yield* CursorInspectionService;
200
+
201
+ const agents = yield* inspection.listAgents({ runtime: "cloud", includeArchived: true });
202
+ const models = yield* inspection.listModels();
203
+ const repos = yield* inspection.listRepositories();
204
+ ```
205
+
206
+ ## Integrate deeper with Effect
207
+
208
+ 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.
209
+
210
+ 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`:
211
+
212
+ ```ts
213
+ import {
214
+ CursorAgentService,
215
+ CursorInspectionService,
216
+ loadCursorConfig,
217
+ liveLayer,
218
+ } from "effect-cursor-sdk";
219
+ import { Effect, Schedule } from "effect";
172
220
 
173
- const agents = yield * inspection.listAgents({ runtime: "cloud", includeArchived: true });
174
- const models = yield * inspection.listModels();
175
- const repos = yield * inspection.listRepositories();
221
+ const agentGardenSnapshot = Effect.gen(function* () {
222
+ const inspection = yield* CursorInspectionService;
223
+ const agents = yield* CursorAgentService;
224
+ const config = yield* loadCursorConfig;
225
+
226
+ const catalog = yield* Effect.all(
227
+ {
228
+ cloud: inspection.listAgents({ runtime: "cloud", includeArchived: false }),
229
+ models: inspection.listModels(),
230
+ repos: inspection.listRepositories(),
231
+ },
232
+ { concurrency: "unbounded" },
233
+ ).pipe(
234
+ Effect.retry(
235
+ Schedule.exponential("150 millis").pipe(Schedule.both(Schedule.recurs(3))),
236
+ ),
237
+ Effect.timeout("45 seconds"),
238
+ );
239
+
240
+ yield* Effect.logInfo("Cursor catalog loaded", {
241
+ cloudAgents: catalog.cloud.items.length,
242
+ models: catalog.models.length,
243
+ repos: catalog.repos.length,
244
+ });
245
+
246
+ const triage = yield* agents.promptFromConfig(
247
+ [
248
+ "You are helping on-call. Here is non-secret inventory:",
249
+ `- Cloud agents (ids): ${catalog.cloud.items.map((a) => a.agentId).join(", ") || "(none)"}`,
250
+ `- Models (ids): ${catalog.models.map((m) => m.id).join(", ") || "(none)"}`,
251
+ `- Repos (urls): ${catalog.repos.map((r) => r.url).join(", ") || "(none)"}`,
252
+ "In two short sentences: what should we verify first before trusting automation here?",
253
+ ].join("\n"),
254
+ config,
255
+ {
256
+ model: { id: "composer-2" },
257
+ },
258
+ );
259
+
260
+ return triage.result;
261
+ }).pipe(Effect.provide(liveLayer));
176
262
  ```
177
263
 
264
+ Swap `liveLayer` for `mockLayer({ ... })` in tests and the same program shape exercises your orchestration without the network.
265
+
178
266
  ## Errors
179
267
 
180
268
  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.
@@ -201,12 +289,13 @@ Never log API keys, MCP credentials, authorization headers, or prompt image data
201
289
  Use `mockLayer` for deterministic tests:
202
290
 
203
291
  ```ts
204
- import { CursorAgentService, mockLayer } from "effect-cursor-sdk";
292
+ import { CursorAgentService, loadCursorConfig, mockLayer } from "effect-cursor-sdk";
205
293
  import { Effect } from "effect";
206
294
 
207
295
  const testProgram = Effect.gen(function* () {
208
296
  const agents = yield* CursorAgentService;
209
- const agent = yield* agents.create({ model: { id: "composer-2" } });
297
+ const config = yield* loadCursorConfig;
298
+ const agent = yield* agents.createFromConfig(config, { model: { id: "composer-2" } });
210
299
  return yield* agents.send(agent, "Hello");
211
300
  }).pipe(
212
301
  Effect.provide(
@@ -221,11 +310,11 @@ const testProgram = Effect.gen(function* () {
221
310
 
222
311
  The main exports are:
223
312
 
224
- - `CursorAgentService`
313
+ - `CursorAgentService` (prefer `createFromConfig`, `scopedFromConfig`, `promptFromConfig`, `resumeFromConfig` with `loadCursorConfig`; plain `AgentOptions` at the agent boundary is [deprecated](./DEPRECATIONS.md))
225
314
  - `CursorRunService`
226
315
  - `CursorArtifactService`
227
316
  - `CursorInspectionService`
228
- - `CursorSdkFactory`
317
+ - `CursorSdkFactory` ([deprecated for application code](./DEPRECATIONS.md#other-deprecations); low-level tests and overrides)
229
318
  - `liveLayer`, `mockLayer`, `liveRuntime`, `makeMockRuntime`
230
319
  - `CursorConfig`, `cursorConfig`, `agentOptionsFromConfig`, `loadCursorConfig`
231
320
  - tagged Cursor error classes and `mapCursorError`
@@ -247,6 +336,10 @@ bun run lint:package
247
336
 
248
337
  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.
249
338
 
339
+ ## Deprecations
340
+
341
+ 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](./DEPRECATIONS.md)**. Pair it with **[CHANGELOG.md](./CHANGELOG.md)** for release-by-release notes; `@deprecated` tags on exported symbols mirror the same intent for day-to-day coding.
342
+
250
343
  ## Versioning and Publishing
251
344
 
252
345
  Use conventional commits for readable history and changelog context: