effect-cursor-sdk 0.2.1 → 0.3.0

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,256 @@
1
+ # Recipes
2
+
3
+ Short patterns for common tasks. Each corresponds to an [`examples/`](../examples) app where a fuller program is useful.
4
+
5
+ **Design note:** This package intentionally keeps the public API centered on the four Effect services (`CursorAgentService`, `CursorRunService`, `CursorArtifactService`, `CursorInspectionService`) plus config, observability, and mocks. Common compositions live here as copy-paste recipes so you always see which service owns each step.
6
+
7
+ ## Config-first agent (preferred)
8
+
9
+ Use {@link loadCursorConfig} with {@link CursorAgentService.scopedFromConfig} or `createFromConfig`:
10
+
11
+ ```ts
12
+ import { CursorAgentService, loadCursorConfig, liveLayer } from "effect-cursor-sdk";
13
+ import { Effect } from "effect";
14
+
15
+ const program = Effect.scoped(
16
+ Effect.gen(function* () {
17
+ const config = yield* loadCursorConfig;
18
+ const agents = yield* CursorAgentService;
19
+ const agent = yield* agents.scopedFromConfig(config, {
20
+ local: { cwd: process.cwd() },
21
+ });
22
+ return yield* agents.send(agent, "Summarize the repo");
23
+ }),
24
+ ).pipe(Effect.provide(liveLayer));
25
+ ```
26
+
27
+ Equivalent merge style (also common in examples):
28
+
29
+ ```ts
30
+ import { agentOptionsFromConfig } from "effect-cursor-sdk";
31
+
32
+ const agent = yield* agents.scoped(agentOptionsFromConfig(config, { local: { cwd: process.cwd() } }));
33
+ ```
34
+
35
+ ## One-shot text from a prompt
36
+
37
+ Use {@link CursorAgentService.promptFromConfig} and read `result` (default to empty string if absent):
38
+
39
+ ```ts
40
+ import { CursorAgentService, liveLayer, loadCursorConfig } from "effect-cursor-sdk";
41
+ import { Effect } from "effect";
42
+
43
+ const program = Effect.gen(function* () {
44
+ const config = yield* loadCursorConfig;
45
+ const agents = yield* CursorAgentService;
46
+ const out = yield* agents.promptFromConfig("Hello", config, { model: { id: "composer-2" } });
47
+ return out.result ?? "";
48
+ }).pipe(Effect.provide(liveLayer));
49
+ ```
50
+
51
+ ## Send and collect assistant text
52
+
53
+ Call {@link CursorAgentService.send}, then {@link CursorRunService.collectText} on the returned run:
54
+
55
+ ```ts
56
+ import { CursorAgentService, CursorRunService, loadCursorConfig, liveLayer } from "effect-cursor-sdk";
57
+ import { Effect } from "effect";
58
+
59
+ const program = Effect.scoped(
60
+ Effect.gen(function* () {
61
+ const config = yield* loadCursorConfig;
62
+ const agents = yield* CursorAgentService;
63
+ const runs = yield* CursorRunService;
64
+ const agent = yield* agents.scopedFromConfig(config, { local: { cwd: process.cwd() } });
65
+ const run = yield* agents.send(agent, "Explain Effect layers");
66
+ return yield* runs.collectText(run);
67
+ }),
68
+ ).pipe(Effect.provide(liveLayer));
69
+ ```
70
+
71
+ ## Streaming with metrics
72
+
73
+ Use {@link streamEventsTracked} or {@link collectTextTracked} with {@link CursorRunService.streamEvents}:
74
+
75
+ ```ts
76
+ import {
77
+ collectTextTracked,
78
+ CursorAgentService,
79
+ CursorRunService,
80
+ loadCursorConfig,
81
+ liveLayer,
82
+ } from "effect-cursor-sdk";
83
+ import { Effect } from "effect";
84
+
85
+ const program = Effect.scoped(
86
+ Effect.gen(function* () {
87
+ const agents = yield* CursorAgentService;
88
+ const runs = yield* CursorRunService;
89
+ const config = yield* loadCursorConfig;
90
+ const agent = yield* agents.scopedFromConfig(config, { local: { cwd: process.cwd() } });
91
+ const run = yield* agents.send(agent, "Hi");
92
+ return yield* collectTextTracked(run, (r) => runs.streamEvents(r));
93
+ }),
94
+ ).pipe(Effect.provide(liveLayer));
95
+ ```
96
+
97
+ ## Status as a stream
98
+
99
+ {@link CursorRunService.streamStatusChanges}:
100
+
101
+ ```ts
102
+ runs.streamStatusChanges(run).pipe(Stream.take(3), Stream.runCollect);
103
+ ```
104
+
105
+ ## Resilient catalog fetch
106
+
107
+ Reuse {@link cursorCatalogRetrySchedule} and {@link cursorCatalogLoadTimeout}:
108
+
109
+ ```ts
110
+ import {
111
+ cursorCatalogLoadTimeout,
112
+ cursorCatalogRetrySchedule,
113
+ CursorInspectionService,
114
+ liveLayer,
115
+ } from "effect-cursor-sdk";
116
+ import { Effect } from "effect";
117
+
118
+ const program = Effect.gen(function* () {
119
+ const inspection = yield* CursorInspectionService;
120
+ return yield* Effect.all(
121
+ {
122
+ agents: inspection.listAgents({ runtime: "cloud" }),
123
+ models: inspection.listModels(),
124
+ },
125
+ { concurrency: "unbounded" },
126
+ ).pipe(Effect.retry(cursorCatalogRetrySchedule), Effect.timeout(cursorCatalogLoadTimeout));
127
+ }).pipe(Effect.provide(liveLayer));
128
+ ```
129
+
130
+ ## Paginated lists
131
+
132
+ `listAgents` / `listRuns` return `nextCursor`. Loop until `nextCursor` is absent (with a `maxPages` guard):
133
+
134
+ ```ts
135
+ import { CursorInspectionService, liveLayer } from "effect-cursor-sdk";
136
+ import type { ListAgentsOptions, ListRunsOptions, Run, SDKAgentInfo } from "effect-cursor-sdk";
137
+ import { Effect } from "effect";
138
+
139
+ const listAgentsAllPages = (base?: ListAgentsOptions, maxPages = 100) =>
140
+ Effect.gen(function* () {
141
+ const inspection = yield* CursorInspectionService;
142
+ const out: SDKAgentInfo[] = [];
143
+ let cursor: string | undefined;
144
+ for (let page = 0; page < maxPages; page++) {
145
+ const res = yield* inspection.listAgents({ ...base, cursor } as ListAgentsOptions);
146
+ out.push(...res.items);
147
+ cursor = res.nextCursor;
148
+ if (cursor === undefined || res.items.length === 0) break;
149
+ }
150
+ return out;
151
+ });
152
+
153
+ const listRunsAllPages = (agentId: string, base?: ListRunsOptions, maxPages = 100) =>
154
+ Effect.gen(function* () {
155
+ const inspection = yield* CursorInspectionService;
156
+ const out: Run[] = [];
157
+ let cursor: string | undefined;
158
+ for (let page = 0; page < maxPages; page++) {
159
+ const res = yield* inspection.listRuns(agentId, { ...base, cursor } as ListRunsOptions);
160
+ out.push(...res.items);
161
+ cursor = res.nextCursor;
162
+ if (cursor === undefined || res.items.length === 0) break;
163
+ }
164
+ return out;
165
+ });
166
+
167
+ // …then e.g. listAgentsAllPages({ runtime: "cloud" }).pipe(Effect.provide(liveLayer));
168
+ ```
169
+
170
+ ## Lifecycle mutations (archive / unarchive / delete)
171
+
172
+ Require operators to type an exact phrase before calling {@link CursorInspectionService.archiveAgent}, `unarchiveAgent`, or `deleteAgent`:
173
+
174
+ ```ts
175
+ import { CursorInspectionService, liveLayer } from "effect-cursor-sdk";
176
+ import { Data, Effect } from "effect";
177
+
178
+ type LifecycleAction = "archive" | "unarchive" | "delete";
179
+
180
+ class LifecycleConfirmationError extends Data.TaggedError("LifecycleConfirmationError")<{
181
+ readonly message: string;
182
+ readonly action: LifecycleAction;
183
+ readonly agentId: string;
184
+ }> {}
185
+
186
+ const lifecyclePhrase = (action: LifecycleAction, agentId: string) => `${action.toUpperCase()} ${agentId}`;
187
+
188
+ const ensureLifecycleConfirmation = (
189
+ action: LifecycleAction,
190
+ agentId: string,
191
+ typed: string,
192
+ ): Effect.Effect<void, LifecycleConfirmationError> => {
193
+ const expected = lifecyclePhrase(action, agentId);
194
+ if (typed !== expected) {
195
+ return Effect.fail(
196
+ new LifecycleConfirmationError({
197
+ message: `Expected confirmation "${expected}".`,
198
+ action,
199
+ agentId,
200
+ }),
201
+ );
202
+ }
203
+ return Effect.void;
204
+ };
205
+
206
+ const archiveAgentConfirmed = (agentId: string, typed: string) =>
207
+ Effect.gen(function* () {
208
+ yield* ensureLifecycleConfirmation("archive", agentId, typed);
209
+ const inspection = yield* CursorInspectionService;
210
+ yield* inspection.archiveAgent(agentId);
211
+ });
212
+
213
+ // archiveAgentConfirmed("agt_1", "ARCHIVE agt_1").pipe(Effect.provide(liveLayer));
214
+ ```
215
+
216
+ Adjust for `unarchiveAgent` / `deleteAgent` the same way.
217
+
218
+ ## Artifacts
219
+
220
+ After {@link CursorArtifactService.listArtifacts}, pick a path (exact, basename, or suffix match) and {@link CursorArtifactService.downloadArtifact}:
221
+
222
+ ```ts
223
+ import { CursorArtifactService, liveLayer } from "effect-cursor-sdk";
224
+ import type { SDKAgent, SDKArtifact } from "effect-cursor-sdk";
225
+ import { Effect } from "effect";
226
+
227
+ function resolveArtifactPath(requested: string, list: ReadonlyArray<SDKArtifact>): string | undefined {
228
+ if (list.some((a) => a.path === requested)) return requested;
229
+ const base = requested.replace(/^.*\//, "");
230
+ return list.find((a) => a.path === base || a.path.endsWith(`/${base}`))?.path;
231
+ }
232
+
233
+ const downloadArtifactResolved = (
234
+ agent: SDKAgent,
235
+ listing: ReadonlyArray<SDKArtifact>,
236
+ requested?: string,
237
+ ) =>
238
+ Effect.gen(function* () {
239
+ const artifacts = yield* CursorArtifactService;
240
+ const path = requested ? resolveArtifactPath(requested, listing) : listing[0]?.path;
241
+ if (path === undefined) return undefined;
242
+ const bytes = yield* artifacts.downloadArtifact(agent, path);
243
+ return { path, bytes } as const;
244
+ });
245
+
246
+ // downloadArtifactResolved(agent, listing, "coverage.html").pipe(Effect.provide(liveLayer));
247
+ ```
248
+
249
+ ## Testing
250
+
251
+ - {@link mockLayer} / {@link makeMockRuntime} for full stack tests.
252
+ - {@link CursorMockFixtures.factoryErrors} to exercise error mapping and retries.
253
+ - {@link CursorMockFixtures.sendSequence} for multi-turn runs.
254
+ - {@link makeMockAssistantSdkMessage} for stream fixtures.
255
+
256
+ See [SDK_COVERAGE.md](./SDK_COVERAGE.md) for wrapper vs re-export policy.
@@ -0,0 +1,46 @@
1
+ # Release checklist
2
+
3
+ Use this before publishing or tagging a release candidate.
4
+
5
+ ## Automated gates
6
+
7
+ From the repository root:
8
+
9
+ ```bash
10
+ bun install
11
+ bun run typecheck
12
+ bun run sdk-audit
13
+ bun run lint
14
+ bun run format:check
15
+ bun run test
16
+ bun run test:coverage
17
+ bun run build
18
+ bun run lint:package
19
+ bun run examples:typecheck
20
+ ```
21
+
22
+ `verify:publish` runs a subset: typecheck, `sdk-audit`, lint, test, build, publint.
23
+
24
+ ## SDK bump
25
+
26
+ When updating `@cursor/sdk` in root `package.json`:
27
+
28
+ 1. `bun install`
29
+ 2. `bun run sdk-audit` — if it fails, inspect new exports (`docs/SDK_COVERAGE.md`, `src/cursor-types.ts`, `CursorSdkFactory`, mocks).
30
+ 3. Align example apps: each `examples/*/package.json` should use the same `@cursor/sdk` range as the library (see peer/dependency policy in `docs/SDK_COVERAGE.md`).
31
+ 4. Refresh `docs/SDK_COVERAGE.md` checklist table if wrappers changed.
32
+ 5. Optionally run `bun run sdk-audit:refresh` **only after** confirming drift is handled, then commit `scripts/sdk-export-baseline.json`.
33
+
34
+ ## Changesets and changelog
35
+
36
+ - Add a [Changeset](https://github.com/changesets/changesets) for user-facing changes: `bun run changeset`
37
+ - User-facing narrative also belongs in `CHANGELOG.md` when cutting a release (via Changesets or manual edit per repo practice).
38
+
39
+ ## Docs
40
+
41
+ - Link new features from [README.md](../README.md).
42
+ - Update [RECIPES.md](./RECIPES.md) when adding important usage patterns
43
+
44
+ ## Credentials note
45
+
46
+ Live Cursor paths are not exercised in CI. After releasing, smoke-test with a disposable API key if you changed agent or networking code.
@@ -0,0 +1,72 @@
1
+ # SDK coverage and compatibility
2
+
3
+ This document is the (somewhat?) **source of truth** for how `effect-cursor-sdk` tracks [@cursor/sdk](https://cursor.com/docs/sdk/typescript). Update it whenever you add wrappers, re-exports, or bump the SDK.
4
+
5
+ ## Policy
6
+
7
+ | Layer | What belongs here |
8
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
9
+ | **Effect-native services** | Each important SDK call gets `Effect.tryPromise` (or `Stream`), `mapCursorError`, and `instrument` via `CursorAgentService`, `CursorRunService`, `CursorArtifactService`, or `CursorInspectionService`. |
10
+ | **Thin factory** | `CursorSdkFactory` mirrors static `Agent.*` / `Cursor.*` entry points for tests and advanced DI. |
11
+ | **Types and utilities** | Curated re-exports in [`src/cursor-types.ts`](../src/cursor-types.ts). Anything else: import from `@cursor/sdk` directly (SDK stays the data-model source of truth). |
12
+ | **Cross-cutting helpers** | Observability presets in [`src/cursor-observability.ts`](../src/cursor-observability.ts). Common multi-step flows belong in [RECIPES.md](./RECIPES.md) as documentation, not extra package exports. |
13
+
14
+ ## Wrapper checklist (maintain when `@cursor/sdk` changes)
15
+
16
+ Mark each row when verified against the **pinned** SDK version in root [`package.json`](../package.json).
17
+
18
+ ### `Agent` static and instances
19
+
20
+ | SDK surface | Wrapper location | Status |
21
+ | ------------------------------------------------- | --------------------------------------------------------------------------- | ------ |
22
+ | `Agent.create` | `CursorAgentService.create` / `createFromConfig`, `CursorSdkFactory.create` | OK |
23
+ | `Agent.resume` | `CursorAgentService.resume` / `resumeFromConfig`, `CursorSdkFactory.resume` | OK |
24
+ | `Agent.prompt` | `CursorAgentService.prompt` / `promptFromConfig`, `CursorSdkFactory.prompt` | OK |
25
+ | `Agent.list` | `CursorInspectionService.listAgents`, `CursorSdkFactory.listAgents` | OK |
26
+ | `Agent.get` | `CursorInspectionService.getAgent`, `CursorSdkFactory.getAgent` | OK |
27
+ | `Agent.archive` / `unarchive` / `delete` | `CursorInspectionService.archiveAgent` / `unarchiveAgent` / `deleteAgent` | OK |
28
+ | `Agent.listRuns` | `CursorInspectionService.listRuns`, `CursorSdkFactory.listRuns` | OK |
29
+ | `Agent.getRun` | `CursorInspectionService.getRun`, `CursorSdkFactory.getRun` | OK |
30
+ | `Agent.messages.list` | `CursorInspectionService.listMessages`, `CursorSdkFactory.listMessages` | OK |
31
+ | `SDKAgent.send` | `CursorAgentService.send` | OK |
32
+ | `SDKAgent.reload` | `CursorAgentService.reload` | OK |
33
+ | `SDKAgent.close` | `CursorAgentService.close` | OK |
34
+ | async dispose | `CursorAgentService.dispose` | OK |
35
+ | `SDKAgent.listArtifacts` | `CursorArtifactService.listArtifacts` | OK |
36
+ | `SDKAgent.downloadArtifact` | `CursorArtifactService.downloadArtifact` | OK |
37
+ | `Run.wait` / `cancel` / `conversation` / `stream` | `CursorRunService` | OK |
38
+ | `Run.supports` / `unsupportedReason` | `CursorRunService.supports` / `unsupportedReason` | OK |
39
+ | `Run.onDidChangeStatus` | `CursorRunService.onDidChangeStatus` (+ `streamStatusChanges` helper) | OK |
40
+
41
+ ### `Cursor` account APIs
42
+
43
+ | SDK surface | Wrapper location | Status |
44
+ | -------------------------- | ------------------------------------------------------------------------------- | ------ |
45
+ | `Cursor.me` | `CursorInspectionService.me`, `CursorSdkFactory.me` | OK |
46
+ | `Cursor.models.list` | `CursorInspectionService.listModels`, `CursorSdkFactory.listModels` | OK |
47
+ | `Cursor.repositories.list` | `CursorInspectionService.listRepositories`, `CursorSdkFactory.listRepositories` | OK |
48
+
49
+ ### Re-exported from `cursor-types`
50
+
51
+ Helpers and errors such as `AuthenticationError`, local run stream decoders, `createAgentPlatform`, etc. See [`src/cursor-types.ts`](../src/cursor-types.ts). New SDK exports are **not** automatic: add them here deliberately to avoid semver surprises.
52
+
53
+ ## Optional audit script
54
+
55
+ From the repo root (after `bun install` so `@cursor/sdk` is present):
56
+
57
+ ```bash
58
+ bun run sdk-audit
59
+ ```
60
+
61
+ The script compares known wrapper operations to exports reachable from `@cursor/sdk` and prints gaps. It is advisory: some SDK exports are types-only or platform binaries.
62
+
63
+ ## Release alignment
64
+
65
+ When bumping `@cursor/sdk`:
66
+
67
+ 1. Run `bun run sdk-audit`.
68
+ 2. Update this checklist if new `Agent` / `Cursor` methods appear.
69
+ 3. Update `CursorSdkFactoryShape`, live implementation, and `makeMockSdkFactoryLayer` mocks in lockstep.
70
+ 4. Run `bun run verify:publish` and `bun run examples:typecheck`.
71
+
72
+ See also [RELEASE_CHECKLIST.md](./RELEASE_CHECKLIST.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cursor-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Effect-based wrapper around the Cursor SDK",
5
5
  "keywords": [
6
6
  "cursor",
@@ -21,7 +21,8 @@
21
21
  "dist",
22
22
  "README.md",
23
23
  "DEPRECATIONS.md",
24
- "LICENSE"
24
+ "LICENSE",
25
+ "docs"
25
26
  ],
26
27
  "type": "module",
27
28
  "sideEffects": false,
@@ -52,15 +53,17 @@
52
53
  "examples:typecheck": "bun run --cwd examples/quickstart typecheck && bun run --cwd examples/cli typecheck && bun run --cwd examples/basic-agent-workflow typecheck && bun run --cwd examples/advanced-ops-dashboard typecheck",
53
54
  "examples:cli:mock": "bun run --cwd examples/cli dev -- --mock \"Summarize the mock response\"",
54
55
  "examples:advanced:mock": "bun run --cwd examples/advanced-ops-dashboard dev -- --mock --triage",
56
+ "sdk-audit": "bun scripts/sdk-surface-audit.ts",
57
+ "sdk-audit:refresh": "bun scripts/sdk-surface-audit.ts --write-baseline",
55
58
  "lint:package": "publint",
56
59
  "test": "vitest run",
57
60
  "test:coverage": "vitest run --coverage",
58
61
  "test:watch": "vitest",
59
62
  "version": "changeset version",
60
- "verify:publish": "bun run typecheck && bun run lint && bun run test && bun run build && bun run lint:package"
63
+ "verify:publish": "bun run typecheck && bun run sdk-audit && bun run lint && bun run test && bun run build && bun run lint:package"
61
64
  },
62
65
  "dependencies": {
63
- "@cursor/sdk": "^1.0.10",
66
+ "@cursor/sdk": "^1.0.12",
64
67
  "effect-cursor-sdk": "."
65
68
  },
66
69
  "devDependencies": {