@stablekernel/opencode-cursor 0.3.0 → 0.4.0-rc.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ - **Fixed: Cursor's `fast` tier is no longer silently forced on.** The variant
8
+ builder only mapped reasoning/effort params and dropped Cursor's `fast` toggle
9
+ entirely, so it never reached `providerOptions.cursor`. Because Cursor marks
10
+ the **default** variant of several models as `fast: true` (composer-2.5,
11
+ composer-2, and the gpt-*-codex line), omitting the param meant opencode
12
+ silently ran the fast tier with no way to opt out. Now `fast` defaults OFF —
13
+ fast-capable models seed `options.params.fast = "false"` (sent every turn, and
14
+ pinned into each reasoning variant so picking a reasoning level can't re-enable
15
+ it) — and a `fast` picker variant lets you opt back in. Override per model via
16
+ `provider.cursor.models.<id>.options.params.fast`.
7
17
  - **Fingerprint-guarded session reuse, now the default (`session: "auto"`).**
8
18
  Previously the provider created a fresh Cursor agent every turn and re-sent
9
19
  the whole transcript (robust but cache-hostile and increasingly costly as a
package/README.md CHANGED
@@ -4,99 +4,57 @@
4
4
  [![CI](https://github.com/stablekernel/opencode-cursor/actions/workflows/ci.yml/badge.svg)](https://github.com/stablekernel/opencode-cursor/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
6
 
7
- An [opencode](https://opencode.ai) plugin that adds a **Cursor** provider backed by the
8
- **official [Cursor SDK](https://cursor.com/docs/sdk/typescript)** (`@cursor/sdk`).
7
+ An [opencode](https://opencode.ai) plugin that adds **Cursor** as a native provider. Your Cursor models appear in the model picker; you chat with them the same way you use any other provider.
9
8
 
10
- It uses your Cursor API key to:
11
-
12
- - register a `cursor` provider in opencode,
13
- - **list the models available to your account** (live, via `Cursor.models.list()`), and
14
- - run chats through Cursor's local agent runtime (`Agent.create` / `agent.send`), streaming
15
- text and reasoning back into opencode (Cursor's own tool activity is surfaced as structured tool
16
- blocks by default; see [Tool display](#tool-display)).
17
-
18
- This plugin registers Cursor as a **native opencode provider**: its models appear in
19
- `opencode models` and the model picker, and you talk to a Cursor model *directly* — with live model
20
- discovery, variants, MCP forwarding, and session reuse. For delegated or background workflows it
21
- also ships two permission-gated tools (`cursor_delegate`, `cursor_cloud_agent`); see
22
- [Provider vs. delegation tools](#provider-vs-delegation-tools).
9
+ It uses the [official Cursor SDK](https://cursor.com/docs/sdk/typescript) (`@cursor/sdk`) to list your account's models live and run chats through Cursor's local agent runtime. For delegated or background workflows it also ships two permission-gated tools (`cursor_delegate`, `cursor_cloud_agent`) — see [Delegation tools](#delegation-tools).
23
10
 
24
11
  > ⚠️ **Security.** When you chat with a `cursor/*` model, Cursor runs its own tools — including
25
12
  > `shell`, `write`, `edit`, and `delete` — directly in your working directory, **outside opencode's
26
- > permission system and unsandboxed by default**. Read [Security](#security) before you use it.
13
+ > permission system**. Read [Security](#security) before you use it.
27
14
 
28
15
  ## Requirements
29
16
 
30
- - **opencode 1.16+** — the provider targets AI SDK `LanguageModelV3`.
31
- - **Node.js 22+ on your `PATH`** — opencode runs on [Bun](https://bun.sh); the plugin spawns a
32
- short-lived Node process to host the Cursor SDK (see
33
- [Runtime: Bun and the Node sidecar](#runtime-bun-and-the-node-sidecar)).
17
+ - **opencode 1.17+**
18
+ - **Node.js 22+ on your `PATH`** — opencode runs on [Bun](https://bun.sh); the plugin needs a
19
+ Node sidecar to host the Cursor SDK (see [Runtime](#runtime-bun-and-the-node-sidecar)).
34
20
  - A **Cursor account and API key** (from the Cursor dashboard).
35
21
 
36
- ## Security
37
-
38
- > ⚠️ **The provider path is unsandboxed and not gated by opencode permissions.**
39
- > When you chat with a `cursor/*` model, Cursor runs its **own** agent loop and executes its own
40
- > tools — including `shell`, `write`, `edit`, and `delete` — directly in your working directory.
41
- > These run **outside** opencode's `permission` system, and the sandbox is **off by default**, so
42
- > your opencode `permission` rules (e.g. `edit: deny`, `bash: ask`) do **not** apply to them. If you
43
- > need an approval boundary, either set `sandbox: true` in `provider.cursor.options` (runs Cursor's
44
- > tools in Cursor's sandbox) or use the permission-gated **`cursor_delegate`** tool instead of the
45
- > provider path. Only the `cursor_delegate` / `cursor_cloud_agent` tools are gated by opencode's
46
- > `permission` config.
47
-
48
- See [SECURITY.md](./SECURITY.md) for the full threat model and how to report a vulnerability.
49
-
50
- ## How it works
51
-
52
- opencode loads two things from this one package:
53
-
54
- | opencode concept | What it loads | Export |
55
- | --- | --- | --- |
56
- | Plugin (`plugin` config) | auth + provider registration + dynamic model listing + a refresh tool | `@stablekernel/opencode-cursor` (resolved via the package's `./server` export) |
57
- | Provider (`provider.cursor.npm`) | a Vercel AI SDK `LanguageModelV3` that drives a local Cursor agent | `@stablekernel/opencode-cursor` (`createCursor`) |
58
-
59
- The plugin's `config` hook registers `provider.cursor` (pointing `npm` at this package) and seeds
60
- it with discovered/fallback models. The `auth` hook stores your API key and feeds it to the
61
- provider factory; the key is validated on first use (model discovery / the first call), not at
62
- login. The `provider.models()` hook refreshes the catalog live once you're authenticated.
63
-
64
22
  ## Install
65
23
 
66
- ### Quick install (one line)
24
+ ### One line
67
25
 
68
26
  ```bash
69
27
  curl -fsSL https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/install.sh | bash
70
28
  ```
71
29
 
72
- The script registers the plugin in your **global** `opencode.json`
73
- (`~/.config/opencode/opencode.json`), checks for Node.js 22+ on your `PATH`, and offers to set
74
- `CURSOR_API_KEY`. Flags:
30
+ Registers the plugin in your global `opencode.json` (`~/.config/opencode/opencode.json`), checks
31
+ for Node.js 22+, and offers to set `CURSOR_API_KEY`. Flags:
75
32
 
76
- - `--project` — write `./opencode.json` in the current directory instead of the global config.
77
- - `--yes` / `-y` — non-interactive; skip all prompts.
33
+ - `--project` — write `./opencode.json` in the current directory instead.
34
+ - `--yes` / `-y` — non-interactive.
78
35
 
79
- It backs up an existing config before editing, is safe to re-run (idempotent), and uses `jq` when
80
- available (falling back to Node). You can review it first:
81
- [`install.sh`](./install.sh).
36
+ [Review the script first.](./install.sh)
82
37
 
83
- ### Manual install
38
+ ### Manual
84
39
 
85
40
  ```bash
86
41
  npm install @stablekernel/opencode-cursor
87
42
  ```
88
43
 
89
- Add the plugin to your `opencode.json` (project or global):
44
+ Add to your `opencode.json` (or `opencode.jsonc` — both are supported):
90
45
 
91
46
  ```json
92
47
  {
93
48
  "$schema": "https://opencode.ai/config.json",
94
- "plugin": ["@stablekernel/opencode-cursor"]
49
+ "plugin": ["@stablekernel/opencode-cursor@latest"]
95
50
  }
96
51
  ```
97
52
 
98
- You do **not** need to hand-write the `provider` block — the plugin injects it. If you prefer to
99
- configure it explicitly, the equivalent is:
53
+ The `@latest` suffix makes opencode re-resolve to the newest release on each
54
+ startup. Drop it (`"@stablekernel/opencode-cursor"`) or pin a version
55
+ (`"@stablekernel/opencode-cursor@1.2.3"`) if you prefer.
56
+
57
+ The plugin injects the `provider` block automatically. If you need explicit control:
100
58
 
101
59
  ```json
102
60
  {
@@ -112,34 +70,44 @@ configure it explicitly, the equivalent is:
112
70
 
113
71
  ## Authenticate
114
72
 
115
- Either run the interactive login:
116
-
117
73
  ```bash
118
74
  opencode auth login # choose "Cursor", paste your key from the Cursor dashboard
119
75
  ```
120
76
 
121
- …or set the environment variable the Cursor SDK reads:
77
+ Or set the environment variable:
122
78
 
123
79
  ```bash
124
80
  export CURSOR_API_KEY="key_..."
125
81
  ```
126
82
 
127
- The key is never logged or written to disk by this plugin. It is validated on first use (model
128
- discovery and the first call surface an invalid key clearly), not at login time.
83
+ The key is validated on first use (model discovery / first call), not at login time.
129
84
 
130
85
  ## Use
131
86
 
132
- - `opencode models` (or the in-app model picker) lists your Cursor models as `cursor/<id>`.
133
- - Pick a Cursor model and chat. The Cursor **local** agent runs in your project working directory.
134
- - Run the `cursor_refresh_models` tool to force a live catalog refresh (bypasses the 24h cache).
87
+ - `opencode models` (or the model picker) lists your Cursor models as `cursor/<id>`.
88
+ - Pick a model and chat the Cursor local agent runs in your project directory.
89
+ - Run the `cursor_refresh_models` tool to force a live catalog refresh.
135
90
 
136
- This plugin also registers two **delegation tools** that complement the provider (see
137
- [Delegation tools](#delegation-tools)):
91
+ The plugin also registers two **delegation tools**:
138
92
 
139
- - `cursor_delegate` — hand a discrete subtask to a local Cursor agent as a permission-gated,
140
- auditable opencode tool call (your primary model stays in control).
141
- - `cursor_cloud_agent` — launch a Cursor **cloud** (background) agent on a remote repo that can run
142
- for minutes and optionally open a PR.
93
+ - `cursor_delegate` — hand a discrete subtask to a local Cursor agent as a permission-gated tool
94
+ call (your primary model stays in control).
95
+ - `cursor_cloud_agent` — launch a Cursor cloud agent on a remote repo that can run for minutes and
96
+ optionally open a PR.
97
+
98
+ ## Security
99
+
100
+ > ⚠️ **The provider path is unsandboxed and not gated by opencode permissions.**
101
+ > When you chat with a `cursor/*` model, Cursor runs its own tools — including `shell`, `write`,
102
+ > `edit`, and `delete` — directly in your working directory. opencode's `permission` rules (e.g.
103
+ > `edit: deny`, `bash: ask`) do **not** apply to them.
104
+ >
105
+ > Options if you need a permission boundary:
106
+ > - Set `sandbox: true` in `provider.cursor.options` to run Cursor's tools in Cursor's sandbox.
107
+ > - Use `cursor_delegate` instead of the provider path — it is gated by opencode's `permission`
108
+ > config.
109
+
110
+ See [SECURITY.md](./SECURITY.md) for the full threat model.
143
111
 
144
112
  ## Configuration
145
113
 
@@ -147,81 +115,58 @@ This plugin also registers two **delegation tools** that complement the provider
147
115
  | --- | --- | --- |
148
116
  | `apiKey` | `CURSOR_API_KEY` | Cursor API key |
149
117
  | `cwd` | `process.cwd()` | Directory the local agent operates in |
150
- | `mode` | `"agent"` | Default Cursor conversation mode (`"agent"` or `"plan"`) |
151
- | `params` | — | Default Cursor model params, `{ <id>: value }` (e.g. `{ thinking: "high" }`) |
152
- | `settingSources` | — | Cursor settings layers to load from disk: `["project","user","all",...]` — pulls in your Cursor **skills**, rules, and `.cursor/mcp.json` |
153
- | `sandbox` | — | Run the agent's tools inside Cursor's sandbox (`true`/`false`) |
154
- | `agents` | — | Cursor subagent definitions (`{ <name>: { description, prompt, model?, mcpServers? } }`) |
155
- | `session` | `"auto"` | Session reuse strategy: `"auto"` (fingerprint-guarded resume), `true` (alias for `"auto"`), or `false` (always fresh). See below |
118
+ | `mode` | `"agent"` | Default conversation mode (`"agent"` or `"plan"`) |
119
+ | `params` | — | Default model params, e.g. `{ thinking: "high" }` |
120
+ | `settingSources` | — | Cursor settings layers to load: `["project","user","all",...]` — pulls in your Cursor skills, rules, and `.cursor/mcp.json` |
121
+ | `sandbox` | — | Run the agent's tools in Cursor's sandbox |
122
+ | `agents` | — | Cursor subagent definitions |
123
+ | `session` | `"auto"` | Session reuse strategy see [Session reuse](#session-reuse-session) |
156
124
  | `forwardMcp` | `true` | Forward opencode's configured MCP servers to the Cursor agent |
157
125
  | `mcpServers` | — | Extra MCP servers (Cursor `McpServerConfig` shape); merged with forwarded ones |
158
- | `toolDisplay` | `"blocks"` | How Cursor's internal tool activity is shown: `"blocks"` (structured provider-executed tool blocks; default, requires opencode 1.16+) or `"reasoning"` (compact lines, the fallback for older/non-V3 hosts). See [Tool display](#tool-display) |
126
+ | `toolDisplay` | `"blocks"` | How Cursor's internal tool activity is shown see [Tool display](#tool-display) |
127
+
128
+ | Environment variable | Default | Meaning |
129
+ | --- | --- | --- |
130
+ | `CURSOR_API_KEY` | — | API key fallback |
131
+ | `OPENCODE_CURSOR_MODEL_CACHE_TTL_MS` | `86400000` | Model-list cache lifetime (ms) |
132
+ | `OPENCODE_CURSOR_DEBUG` | — | Set to `1` for trace logging on stderr |
133
+ | `OPENCODE_CURSOR_SIDECAR` | — | `1` = always use Node sidecar; `0` = never |
159
134
 
160
135
  ### Session reuse (`session`)
161
136
 
162
- opencode re-sends the **entire** conversation transcript on every turn. Replaying that into a fresh
163
- Cursor agent each turn is robust but costs more input tokens as the conversation grows (and pays
164
- opencode's system prompt on top of Cursor's own). Reusing one Cursor agent and sending only the new
165
- message is the cache-friendly, native-CLI-like path — but a blindly resumed agent can drift from
166
- opencode's view of history (message edits, reverts, opencode-side compaction) and must not be
167
- disturbed by opencode's non-chat side calls (e.g. title generation).
137
+ opencode re-sends the full conversation transcript on every turn. `session: "auto"` (the default)
138
+ fingerprints the conversation and resumes the same Cursor agent when nothing has changed, so you
139
+ only pay for the new message. It falls back to a fresh agent + full transcript on edits, reverts,
140
+ or compaction.
168
141
 
169
- **`session: "auto"` (the default) resolves this with a per-turn fingerprint.** The provider hashes
170
- only the parts opencode replays verbatim — the system prompt and the user-message sequence — and
171
- classifies each turn:
142
+ | Situation | What happens |
143
+ | --- | --- |
144
+ | First turn | Fresh agent, full transcript, pool it |
145
+ | System prompt differs (title gen, other side calls) | Ephemeral fresh agent; pooled agent untouched |
146
+ | Clean continuation (one new user message) | `Agent.resume` — sends only the new message |
147
+ | Forwarded MCP server set changed | Fresh agent + full transcript, re-pooled |
148
+ | Message edited/reverted or conversation compacted | Fresh agent + full transcript, re-pooled |
172
149
 
173
- | Situation | Classification | What the provider does |
174
- | --- | --- | --- |
175
- | First turn of the session | **new** | fresh agent, full transcript, pool it |
176
- | System prompt differs (title gen and other side calls) | **side-call** | fresh ephemeral agent; the pooled agent is left untouched |
177
- | Prior user sequence is an exact prefix + exactly one new user message | **continuation** | `Agent.resume` the pooled agent, send **only** the new message |
178
- | Continuation, but the forwarded MCP server set changed | **continuation** (fresh agent) | fresh agent + full transcript, re-pool — a resumed agent keeps its original MCP servers, so a fresh one is needed for the new set |
179
- | Earlier message edited/reverted, conversation compacted, or several messages queued | **divergence** | fresh agent, full transcript, re-pool |
180
-
181
- The worst case on any misclassification is a single full-transcript replay that self-heals on the
182
- next turn — never worse than `session: false`. A failed resume also degrades to a fresh replay. The
183
- resumed agent is named after the session and visible in Cursor's dashboard; the opencode session id
184
- reaches the provider via the plugin's `chat.params` hook (`providerOptions.cursor.sessionID`).
185
- Fingerprint records persist (best-effort) to `~/.cache/opencode-cursor/session-pool.json`, so
186
- session reuse survives opencode restarts — the conversation itself lives in Cursor's own local
187
- checkpoint store, and the next turn resumes it instead of replaying the transcript.
188
-
189
- - `session: true` is an alias for `"auto"`.
190
- - `session: false` restores the original behavior: always a fresh agent + full transcript, every
191
- turn. Use it if you want each turn fully independent.
192
-
193
- **Cache implications.** Cursor builds prompts cache-friendly and the model provider's own prefix
194
- cache (Anthropic uses a ~5-minute sliding TTL) decides hits. `"auto"` keeps the prompt prefix stable
195
- across turns, which is what lands cache reads instead of expensive re-seeds. Things that re-seed the
196
- cache even mid-window: switching model/variant, changing the thinking level, toggling agent/plan
197
- mode, editing an earlier message, or changing the forwarded MCP server set (tool definitions sit at
198
- the top of the provider's cache-prefix hierarchy, so they invalidate everything after them). Tool outputs from earlier
199
- turns are included (truncated) in the replay paths so a fresh/diverged agent still sees what prior
200
- tools produced. Set `OPENCODE_CURSOR_DEBUG=1` to log the per-turn classification and the
201
- `cacheReadTokens`/`cacheWriteTokens` reported by Cursor.
150
+ `session: true` is an alias for `"auto"`. `session: false` disables reuse (always fresh agent,
151
+ full transcript every turn).
202
152
 
203
- ### Per-request controls (`mode`, thinking level)
153
+ Fingerprint records persist to `~/.cache/opencode-cursor/session-pool.json`, so session reuse
154
+ survives opencode restarts.
204
155
 
205
- opencode delivers per-request, provider-specific settings to the model under
206
- `providerOptions.cursor`. This plugin reads:
156
+ ### Per-request controls (`mode`, thinking level)
207
157
 
208
- - `mode` `"agent"` | `"plan"`
209
- - `params` `{ <paramId>: value }` mapped to Cursor `ModelSelection.params`
210
- - `thinking` convenience, mapped to the `thinking` param
158
+ The plugin auto-generates model variants for each reasoning/effort level a model advertises,
159
+ plus a `fast` toggle for models that expose Cursor's fast tier. Selecting a variant in the
160
+ model picker sends its settings through `providerOptions.cursor`.
211
161
 
212
- These are most naturally driven by opencode's **model variant picker**: the plugin auto-generates
213
- one variant per reasoning/effort level a model advertises (`Cursor.models.list()` parameters). A
214
- boolean parameter (e.g. `thinking: ["false","true"]`) collapses to a single variant named after the
215
- parameter that switches it on (the off state is the default — no variant selected); enum parameters
216
- (e.g. `effort`, `reasoning`) produce one variant per value. Selecting a variant sends its settings
217
- through `providerOptions.cursor`.
162
+ `fast` defaults **off** (even though Cursor's own default is `fast: true` for some models, e.g.
163
+ Composer and the codex line) so opencode never silently runs the fast tier — pick the `fast`
164
+ variant to opt in, or set it per model under `options.params.fast` below.
218
165
 
219
- > **Plan mode is not a variant.** opencode's **plan agent** (toggled with `Tab`) is mapped to
220
- > Cursor's plan mode automatically by the plugin's `chat.params` hook, so switching opencode into
221
- > plan mode puts the Cursor agent into plan mode too. An explicit `mode` from a selected variant or
222
- > model option still wins.
166
+ opencode's **plan agent** (`Tab`) maps to Cursor's plan mode automatically — no manual config
167
+ needed.
223
168
 
224
- You can also set controls statically per model:
169
+ To set controls statically per model:
225
170
 
226
171
  ```json
227
172
  { "provider": { "cursor": { "models": {
@@ -229,56 +174,30 @@ You can also set controls statically per model:
229
174
  } } } }
230
175
  ```
231
176
 
232
- | Environment variable | Default | Meaning |
233
- | --- | --- | --- |
234
- | `CURSOR_API_KEY` | — | API key fallback |
235
- | `OPENCODE_CURSOR_MODEL_CACHE_TTL_MS` | `86400000` | Model-list cache lifetime |
236
-
237
- To disable MCP forwarding, set `provider.cursor.options.forwardMcp: false` in your opencode config.
238
-
239
177
  ## MCP servers
240
178
 
241
- The Cursor agent can use the **same MCP servers you've configured in opencode**. Forwarding is
242
- **live, per turn**: the plugin's `chat.params` hook reads opencode's current MCP state
243
- (`client.mcp.status()` for what's actually enabled right now, `client.config.get()` for the launch
244
- specs), translates each entry into the Cursor SDK's `McpServerConfig` shape, and hands the set to
245
- the agent — so enabling or disabling an MCP server mid-session takes effect on the next turn, not
246
- the next restart. A startup snapshot from the `config` hook remains as the fallback when the live
247
- read is unavailable.
179
+ With `forwardMcp: true` (default), the Cursor agent uses the same MCP servers configured in
180
+ opencode. The server list is updated live per turn, so enabling or disabling an MCP server takes
181
+ effect on the next message.
248
182
 
249
183
  | opencode `config.mcp` | → Cursor |
250
184
  | --- | --- |
251
- | `{ type: "local", command: [cmd, ...args], environment }` | `{ type: "stdio", command: cmd, args, env }` |
185
+ | `{ type: "local", command: [cmd, ...args], environment }` | `{ type: "stdio", command, args, env }` |
252
186
  | `{ type: "remote", url, headers }` | `{ type: "http", url, headers }` |
253
- | remote with registered OAuth client (`clientId`, optional secret/scopes) | `{ type: "http", url, auth: { CLIENT_ID, … } }` — the agent runs its own OAuth flow |
254
-
255
- So whatever MCP servers your `opencode.json` defines, your Cursor agent connects to those same
256
- servers — MCP servers are independent processes, so opencode and the agent each connect to them
257
- directly.
258
- Disabled entries (`enabled: false`) are skipped. Turn this off with `forwardMcp: false`.
187
+ | Remote with registered OAuth `clientId` | `{ type: "http", url, auth: { CLIENT_ID, … } }` |
259
188
 
260
- > **OAuth caveat.** opencode's own access tokens never land in `config.mcp`, so a remote server
261
- > that needs OAuth **without** a shareable `clientId` (dynamic client registration / `needs_auth`)
262
- > can't be forwarded — forwarding its spec would just 401. Such servers are skipped and a one-time
263
- > toast tells you which ones; they keep working inside opencode itself.
264
- >
265
- > **Session-reuse interaction.** A resumed Cursor agent keeps the MCP servers it was created with,
266
- > so when the forwarded set changes between turns the provider creates a fresh agent (full
267
- > transcript replay, re-pooled) instead of resuming — see
268
- > [Session reuse](#session-reuse-session). Tool definitions sit at the top of the provider's
269
- > cache-prefix hierarchy, so an MCP change also re-seeds the prompt cache.
189
+ Disabled entries (`enabled: false`) are skipped. Remote servers requiring OAuth without a
190
+ shareable `clientId` are also skipped (a one-time toast says which). Disable forwarding with
191
+ `forwardMcp: false`.
270
192
 
271
- > Scope note: this forwards **MCP servers**. opencode's *loop-internal* features its own skills
272
- > and subagents — are not exposed to the Cursor agent (they run inside opencode's agent loop, which
273
- > this provider bypasses). The Cursor agent's *own* skills/rules can be loaded with the
274
- > `settingSources` option (e.g. `["project","user"]`), which reads your local Cursor configuration.
193
+ > **Note:** This forwards MCP **servers**. opencode's own skills and subagents are not exposed to
194
+ > the Cursor agent. To load your local Cursor skills/rules, use
195
+ > `settingSources: ["project","user"]`.
275
196
 
276
197
  ## Delegation tools
277
198
 
278
- Alongside the provider, the plugin registers two tools so it is a **superset** of both the
279
- provider and delegated-tool designs. Both resolve the Cursor API key from your `opencode auth login`
280
- session (or `CURSOR_API_KEY`) and are **permission-gated** via opencode's `permission` config — they
281
- call `context.ask`, so a policy of `allow` runs silently, `ask` prompts, and `deny` blocks:
199
+ Both tools resolve the API key from your `opencode auth login` session (or `CURSOR_API_KEY`) and
200
+ are gated by opencode's `permission` config:
282
201
 
283
202
  ```json
284
203
  { "permission": { "cursor_delegate": "ask", "cursor_cloud_agent": "ask" } }
@@ -286,176 +205,86 @@ call `context.ask`, so a policy of `allow` runs silently, `ask` prompts, and `de
286
205
 
287
206
  ### `cursor_delegate` (local)
288
207
 
289
- Run a single Cursor turn on a fresh (or explicitly resumed) **local** agent and return its result.
290
- Use it when your primary opencode model should stay in control and hand off discrete work to Cursor
291
- as an explicit, auditable tool call.
208
+ Runs one Cursor turn as a permission-gated tool call. Your primary opencode model hands off a
209
+ discrete subtask and gets the result back.
292
210
 
293
211
  | Arg | Required | Meaning |
294
212
  | --- | --- | --- |
295
213
  | `prompt` | ✅ | The subtask to delegate |
296
- | `model` | ✅ | Cursor model id to run on |
214
+ | `model` | ✅ | Cursor model id |
297
215
  | `mode` | — | `"agent"` or `"plan"` |
298
216
  | `thinking` | — | Thinking level (e.g. `"high"`) |
299
- | `cwd` | — | Working directory (defaults to the session directory) |
300
- | `sandbox` | — | Run the agent's tools in Cursor's sandbox |
301
- | `agentId` | — | Resume a specific Cursor agent id instead of starting fresh |
217
+ | `cwd` | — | Working directory |
218
+ | `sandbox` | — | Run in Cursor's sandbox |
219
+ | `agentId` | — | Resume a specific Cursor agent |
302
220
 
303
- ### `cursor_cloud_agent` (cloud / background)
221
+ ### `cursor_cloud_agent` (cloud)
304
222
 
305
- Launch a Cursor **cloud** agent against a remote repository. It runs autonomously (potentially for
306
- minutes) and can open a pull request — work that maps poorly onto the synchronous provider path, so
307
- it is exposed as a tool. Returns the cloud agent id, terminal status, result text, and PR url.
223
+ Launches a background Cursor cloud agent on a remote repo. Can run for minutes and optionally
224
+ open a PR.
308
225
 
309
226
  | Arg | Required | Meaning |
310
227
  | --- | --- | --- |
311
- | `prompt` | ✅ | The task for the background agent |
228
+ | `prompt` | ✅ | The task |
312
229
  | `repoUrl` | ✅ | Target repository URL (e.g. `https://github.com/owner/repo`) |
313
- | `startingRef` | — | Branch/ref to start from (defaults to the repo default) |
314
- | `model` | — | Cursor model id (optional for cloud) |
230
+ | `startingRef` | — | Branch/ref to start from |
231
+ | `model` | — | Cursor model id |
315
232
  | `mode` | — | `"agent"` or `"plan"` |
316
- | `thinking` | — | Thinking level (e.g. `"high"`) |
317
- | `autoCreatePR` | — | Open a PR automatically when finished |
233
+ | `thinking` | — | Thinking level |
234
+ | `autoCreatePR` | — | Open a PR when finished |
318
235
  | `workOnCurrentBranch` | — | Operate on the current branch instead of a new one |
319
236
 
320
- ## Provider vs. delegation tools
321
-
322
- This package ships two complementary ways to use Cursor inside opencode:
237
+ ## Tool display
323
238
 
324
- - **Provider** (`cursor/*` models) chat with a Cursor model directly, integrated into opencode's
325
- normal model/variant UX, with live model discovery, MCP forwarding, and session reuse. Cursor runs
326
- its own tools internally (surfaced per the [`toolDisplay`](#tool-display) option).
327
- - **Delegation tools** — `cursor_delegate` hands a discrete subtask to a local Cursor agent as a
328
- permission-gated, auditable tool call (your primary opencode model stays in control); and
329
- `cursor_cloud_agent` launches a background agent on a remote repo that can run for minutes and
330
- optionally open a PR.
239
+ `toolDisplay` controls how Cursor's internal tool activity appears in opencode:
331
240
 
332
- **When to use which.** Use the **provider** when you want Cursor to *be* a model you select and
333
- converse with, integrated into opencode's normal model/variant UX. Use **`cursor_delegate`** when
334
- you want your existing opencode model to stay in control and hand off discrete tasks as explicit,
335
- permission-gated tool calls. Use **`cursor_cloud_agent`** when you need background work on a remote
336
- repo with optional PR creation.
337
-
338
- ## Behavior & limitations
339
-
340
- > The provider path runs Cursor's own unsandboxed tools outside opencode's permission system — see
341
- > [Security](#security).
342
-
343
- This plugin runs Cursor as a **local agent** (`Agent.create({ local: { cwd } })`), so:
344
-
345
- - **Cursor executes its own tools** (read/write/edit/shell/grep/mcp/…) directly in your working
346
- directory. How that activity is shown is controlled by the [`toolDisplay`](#tool-display) option.
347
- Either way it is **not** routed through opencode's tool/permission system — Cursor runs the tools
348
- itself.
349
- - By default (`session: "auto"`) the provider resumes one Cursor agent per session and sends only
350
- the new message on a clean continuation, falling back to a fresh agent + full transcript on
351
- edits/reverts/compaction/side calls (see [Session reuse](#session-reuse-session)). Set
352
- `session: false` to always create a fresh agent and re-send the full transcript every turn.
353
- - Token usage is reported from Cursor's `turn-ended` event; cost is shown as `0` because Cursor
354
- bills your account separately.
355
- - **Provider path is local.** The `cursor/*` models you chat with run as a **local** agent. Cursor's
356
- **cloud** runtime (background agents on a remote repo with optional PR creation) maps awkwardly
357
- onto a synchronous provider call, so it is exposed as the `cursor_cloud_agent` **tool** instead of
358
- the provider path — see [Delegation tools](#delegation-tools).
359
-
360
- ### Runtime: Bun and the Node sidecar
361
-
362
- opencode runs on [Bun](https://bun.sh). Bun's `node:http2` client is currently incompatible with the
363
- Cursor SDK's long-lived streaming RPC (it aborts the stream with `NGHTTP2_FRAME_SIZE_ERROR`), which
364
- makes Cursor's native tool calls execute but never report completion — they appear stuck or show
365
- "Tool execution aborted".
366
-
367
- To work around this transparently, when the plugin detects it is running under Bun and finds `node`
368
- on your `PATH`, it hosts the Cursor SDK agent in a short-lived **Node child process** (a "sidecar")
369
- and talks to it over stdio. Behavior is otherwise identical. Under Node the SDK runs in-process and
370
- no sidecar is spawned.
371
-
372
- - **Requirement:** a Node.js runtime on `PATH` when running under Bun — Node 22+ to match `engines`
373
- (the plugin checks that `node` is present, not its version). If Bun is detected but `node` is
374
- missing, the plugin logs a one-time warning and falls back to in-process (native Cursor tools will
375
- misbehave until Node is available).
376
- - **Override** with the `OPENCODE_CURSOR_SIDECAR` environment variable:
377
- - `OPENCODE_CURSOR_SIDECAR=1` — always use the sidecar (requires `node`).
378
- - `OPENCODE_CURSOR_SIDECAR=0` — never use the sidecar / silence the Bun warning.
241
+ - **`"blocks"` (default)** structured, collapsible tool blocks with inputs and outputs. Common
242
+ Cursor tools are mapped to their opencode equivalents (`edit` diff viewer, `shell` → bash
243
+ console, etc.). Requires opencode 1.17+.
244
+ - **`"reasoning"`** compact inline lines (`[tool] write {"path":…}`). Works on any host; use
245
+ this on older opencode versions.
379
246
 
380
- ## Tool display
247
+ To force the fallback:
381
248
 
382
- Cursor runs its own agent loop and executes its own tools. The `toolDisplay` option controls how
383
- that activity appears in opencode:
384
-
385
- - **`"blocks"` (default)** — tool activity is emitted as structured, **provider-executed**
386
- `tool-call`/`tool-result` parts so opencode renders proper, collapsible tool blocks with inputs
387
- and outputs. opencode skips execution for provider-executed calls (they're display-only), so
388
- Cursor's tools (`shell`, `mcp`, …) don't trigger an "unavailable tool" error. Requires a
389
- V3-native opencode host (1.16+).
390
-
391
- Where a Cursor tool has a natural opencode counterpart, it's surfaced under opencode's
392
- **registered** tool name so its native renderer is used instead of a generic block: `edit` →
393
- opencode's diff viewer (via `metadata.diff`), `shell` → `bash` console, `task` → the subagent
394
- card, web search (which Cursor runs as an MCP tool) → the `websearch` renderer, and
395
- `read`/`write`/`glob`/`grep`/`ls`/`updateTodos` → opencode's
396
- `read`/`write`/`glob`/`grep`/`list`/`todowrite` renderers. Cursor's arg shape is translated to
397
- opencode's (e.g. `path` → `filePath`); the call stays provider-executed, so it's display-only and
398
- never re-run on disk.
399
-
400
- Tools with no opencode counterpart still get cleaned up: `readLints` and `delete` render as
401
- formatted `cursor_*` blocks (a diagnostics list / a one-line confirmation) rather than raw JSON,
402
- and any MCP tool's `content` is flattened to readable text. Anything else — or a result with an
403
- unexpected shape — falls back to a prefixed `cursor_*` block with the raw payload.
404
- - **`"reasoning"` (fallback)** — each tool call is shown as a compact reasoning line
405
- (`[tool] write {"path":…}`; failures as `[tool] x failed`). Robust on every host: no tool-call
406
- parts cross into opencode, so there's no dependency on how the host treats provider-executed
407
- tools. Use this on older/non-V3 opencode hosts.
408
-
409
- The default needs no configuration. To force the reasoning fallback (e.g. on a pre-1.16 host):
410
-
411
- ```jsonc
412
- {
413
- "provider": {
414
- "cursor": {
415
- "options": { "toolDisplay": "reasoning" }
416
- }
417
- }
418
- }
249
+ ```json
250
+ { "provider": { "cursor": { "options": { "toolDisplay": "reasoning" } } } }
419
251
  ```
420
252
 
421
- > Why blocks by default: structured tool blocks are the nicer experience and have been verified
422
- > against opencode 1.16+. `"blocks"` depends on V3-native, provider-executed dynamic tool parts; if
423
- > your host predates that (or renders them poorly), set `"toolDisplay": "reasoning"` it requires
424
- > nothing from the host and works everywhere.
253
+ ## Runtime: Bun and the Node sidecar
254
+
255
+ opencode runs on [Bun](https://bun.sh), which has an `node:http2` incompatibility with the Cursor
256
+ SDK's streaming RPC. The plugin transparently hosts the Cursor SDK in a short-lived **Node child
257
+ process** when running under Bun. Under Node it runs in-process.
258
+
259
+ This is why **Node.js 22+ on your `PATH`** is required. If Node isn't found, the plugin warns once
260
+ and falls back to in-process (native Cursor tools will misbehave until Node is available).
261
+
262
+ Override with `OPENCODE_CURSOR_SIDECAR=1` (always sidecar) or `OPENCODE_CURSOR_SIDECAR=0` (never).
425
263
 
426
264
  ## Troubleshooting
427
265
 
428
- - **Native Cursor tools hang or show "Tool execution aborted" (`NGHTTP2_FRAME_SIZE_ERROR`).** This
429
- is the Bun `node:http2` incompatibility. Make sure **Node.js is installed and on your `PATH`** so
430
- the plugin can use the Node sidecar (see [Runtime](#runtime-bun-and-the-node-sidecar)); force it
431
- with `OPENCODE_CURSOR_SIDECAR=1`.
266
+ - **Native Cursor tools hang / "Tool execution aborted" (`NGHTTP2_FRAME_SIZE_ERROR`).** Node isn't
267
+ on your `PATH`. Install Node.js 22+, or force the sidecar with `OPENCODE_CURSOR_SIDECAR=1`.
432
268
  - **"Running under Bun without a usable Node sidecar" warning.** Install Node.js 22+, or set
433
269
  `OPENCODE_CURSOR_SIDECAR=0` to accept in-process behavior and silence the warning.
434
- - **"Could not locate the bindings file" / `node_sqlite3.node` not found.** `@cursor/sdk` depends on
435
- the native `sqlite3` addon, and opencode installs plugins with Bun, which skips sqlite3's install
436
- script so the prebuilt binary may be missing. The plugin detects this and self-heals on first SDK
437
- load by running sqlite3's own `prebuild-install -r napi` under your system Node (requires Node on
438
- `PATH`). If it can't (no Node, offline), it logs a one-line manual fix: `cd` into the printed
439
- sqlite3 directory and run `npx prebuild-install -r napi` (or `npm rebuild sqlite3`). Set
440
- `OPENCODE_CURSOR_DEBUG=1` to see the repair output.
441
- - **Plugin looks enabled but no `cursor` provider/models appear.** opencode caches a plugin by its
442
- install spec under `~/.cache/opencode/packages/`; a stale cache from an older version can persist.
443
- Pin an exact version (`@stablekernel/opencode-cursor@<version>`) or delete the cached dir and
444
- restart so opencode reinstalls.
445
- - **Only the four fallback models appear in the picker.** The live catalog loads after the first
446
- authenticated use — restart opencode once after logging in, or run `cursor_refresh_models` to
447
- force a refresh.
448
- - **Invalid or expired key.** The key is validated on first use (model discovery / first call), not
449
- at login, so that's where an error surfaces.
450
- - **Need more detail?** Set `OPENCODE_CURSOR_DEBUG=1` for provider and sidecar trace logging on
451
- stderr.
270
+ - **"Could not locate the bindings file" / `node_sqlite3.node` not found.** The `@cursor/sdk`
271
+ native sqlite3 addon was skipped during Bun install. The plugin self-heals on first load (needs
272
+ Node on `PATH`). If that fails, `cd` into the printed sqlite3 directory and run
273
+ `npx prebuild-install -r napi`.
274
+ - **Plugin enabled but no `cursor` provider/models appear.** Stale opencode plugin cache. Pin an
275
+ exact version (`@stablekernel/opencode-cursor@<version>`) or delete
276
+ `~/.cache/opencode/packages/` and restart.
277
+ - **Only the four fallback models appear.** The live catalog loads after the first authenticated
278
+ use. Restart opencode once after login, or run `cursor_refresh_models`.
279
+ - **Invalid or expired key.** Validated on first use — that's where the error surfaces.
280
+ - **Need more detail?** Set `OPENCODE_CURSOR_DEBUG=1`.
452
281
 
453
282
  ## Contributing
454
283
 
455
- Issues and pull requests are welcome. See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for development
456
- setup, the test/typecheck/build commands, and the release process. Please report bugs at the
284
+ Issues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for dev setup,
285
+ test/typecheck/build commands, and the release process. Report bugs at the
457
286
  [issue tracker](https://github.com/stablekernel/opencode-cursor/issues); for security reports
458
- see **[SECURITY.md](./SECURITY.md)**.
287
+ see [SECURITY.md](./SECURITY.md).
459
288
 
460
289
  ## License
461
290