@stablekernel/opencode-cursor 0.2.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/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.
90
+
91
+ The plugin also registers two **delegation tools**:
92
+
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.
135
97
 
136
- This plugin also registers two **delegation tools** that complement the provider (see
137
- [Delegation tools](#delegation-tools)):
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.
138
109
 
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.
110
+ See [SECURITY.md](./SECURITY.md) for the full threat model.
143
111
 
144
112
  ## Configuration
145
113
 
@@ -147,48 +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` | `false` | Reuse one Cursor agent per opencode session (resume across turns; 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
- By default each opencode turn spins up a **fresh** Cursor agent and re-sends the full conversation
163
- transcript robust, and correct even for opencode's non-chat calls (e.g. title generation). Set
164
- `session: true` to instead keep **one Cursor agent per opencode session**: the provider names the
165
- agent after the session, `Agent.resume()`s it on later turns, and sends only the new message so
166
- Cursor uses its native conversation memory and checkpoints (the agent is visible in Cursor's
167
- dashboard). The opencode session id reaches the provider via the plugin's `chat.params` hook
168
- (`providerOptions.cursor.sessionID`); a failed resume falls back to a fresh turn automatically.
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.
169
141
 
170
- ### Per-request controls (`mode`, thinking level)
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 |
149
+
150
+ `session: true` is an alias for `"auto"`. `session: false` disables reuse (always fresh agent,
151
+ full transcript every turn).
171
152
 
172
- opencode delivers per-request, provider-specific settings to the model under
173
- `providerOptions.cursor`. This plugin reads:
153
+ Fingerprint records persist to `~/.cache/opencode-cursor/session-pool.json`, so session reuse
154
+ survives opencode restarts.
155
+
156
+ ### Per-request controls (`mode`, thinking level)
174
157
 
175
- - `mode` `"agent"` | `"plan"`
176
- - `params` `{ <paramId>: value }` mapped to Cursor `ModelSelection.params`
177
- - `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`.
178
161
 
179
- These are most naturally driven by opencode's **model variant picker**: the plugin auto-generates
180
- one variant per reasoning/effort level a model advertises (`Cursor.models.list()` parameters). A
181
- boolean parameter (e.g. `thinking: ["false","true"]`) collapses to a single variant named after the
182
- parameter that switches it on (the off state is the default — no variant selected); enum parameters
183
- (e.g. `effort`, `reasoning`) produce one variant per value. Selecting a variant sends its settings
184
- 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.
185
165
 
186
- > **Plan mode is not a variant.** opencode's **plan agent** (toggled with `Tab`) is mapped to
187
- > Cursor's plan mode automatically by the plugin's `chat.params` hook, so switching opencode into
188
- > plan mode puts the Cursor agent into plan mode too. An explicit `mode` from a selected variant or
189
- > model option still wins.
166
+ opencode's **plan agent** (`Tab`) maps to Cursor's plan mode automatically — no manual config
167
+ needed.
190
168
 
191
- You can also set controls statically per model:
169
+ To set controls statically per model:
192
170
 
193
171
  ```json
194
172
  { "provider": { "cursor": { "models": {
@@ -196,39 +174,30 @@ You can also set controls statically per model:
196
174
  } } } }
197
175
  ```
198
176
 
199
- | Environment variable | Default | Meaning |
200
- | --- | --- | --- |
201
- | `CURSOR_API_KEY` | — | API key fallback |
202
- | `OPENCODE_CURSOR_MODEL_CACHE_TTL_MS` | `86400000` | Model-list cache lifetime |
203
-
204
- To disable MCP forwarding, set `provider.cursor.options.forwardMcp: false` in your opencode config.
205
-
206
177
  ## MCP servers
207
178
 
208
- The Cursor agent can use the **same MCP servers you've configured in opencode**. The plugin's
209
- `config` hook reads opencode's `config.mcp`, translates each entry into the Cursor SDK's
210
- `McpServerConfig` shape, and hands them to the agent via `Agent.create({ mcpServers })`:
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.
211
182
 
212
183
  | opencode `config.mcp` | → Cursor |
213
184
  | --- | --- |
214
- | `{ type: "local", command: [cmd, ...args], environment }` | `{ type: "stdio", command: cmd, args, env }` |
185
+ | `{ type: "local", command: [cmd, ...args], environment }` | `{ type: "stdio", command, args, env }` |
215
186
  | `{ type: "remote", url, headers }` | `{ type: "http", url, headers }` |
187
+ | Remote with registered OAuth `clientId` | `{ type: "http", url, auth: { CLIENT_ID, … } }` |
216
188
 
217
- So if your `opencode.json` defines Serena, your Cursor agent connects to that same Serena — MCP
218
- servers are independent processes, so opencode and the agent each connect to them directly.
219
- Disabled entries (`enabled: false`) are skipped. Turn this off with `forwardMcp: false`.
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`.
220
192
 
221
- > Scope note: this forwards **MCP servers**. opencode's *loop-internal* features its own skills
222
- > and subagents — are not exposed to the Cursor agent (they run inside opencode's agent loop, which
223
- > this provider bypasses). The Cursor agent's *own* skills/rules can be loaded with the
224
- > `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"]`.
225
196
 
226
197
  ## Delegation tools
227
198
 
228
- Alongside the provider, the plugin registers two tools so it is a **superset** of both the
229
- provider and delegated-tool designs. Both resolve the Cursor API key from your `opencode auth login`
230
- session (or `CURSOR_API_KEY`) and are **permission-gated** via opencode's `permission` config — they
231
- 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:
232
201
 
233
202
  ```json
234
203
  { "permission": { "cursor_delegate": "ask", "cursor_cloud_agent": "ask" } }
@@ -236,175 +205,86 @@ call `context.ask`, so a policy of `allow` runs silently, `ask` prompts, and `de
236
205
 
237
206
  ### `cursor_delegate` (local)
238
207
 
239
- Run a single Cursor turn on a fresh (or explicitly resumed) **local** agent and return its result.
240
- Use it when your primary opencode model should stay in control and hand off discrete work to Cursor
241
- 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.
242
210
 
243
211
  | Arg | Required | Meaning |
244
212
  | --- | --- | --- |
245
213
  | `prompt` | ✅ | The subtask to delegate |
246
- | `model` | ✅ | Cursor model id to run on |
214
+ | `model` | ✅ | Cursor model id |
247
215
  | `mode` | — | `"agent"` or `"plan"` |
248
216
  | `thinking` | — | Thinking level (e.g. `"high"`) |
249
- | `cwd` | — | Working directory (defaults to the session directory) |
250
- | `sandbox` | — | Run the agent's tools in Cursor's sandbox |
251
- | `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 |
252
220
 
253
- ### `cursor_cloud_agent` (cloud / background)
221
+ ### `cursor_cloud_agent` (cloud)
254
222
 
255
- Launch a Cursor **cloud** agent against a remote repository. It runs autonomously (potentially for
256
- minutes) and can open a pull request — work that maps poorly onto the synchronous provider path, so
257
- 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.
258
225
 
259
226
  | Arg | Required | Meaning |
260
227
  | --- | --- | --- |
261
- | `prompt` | ✅ | The task for the background agent |
228
+ | `prompt` | ✅ | The task |
262
229
  | `repoUrl` | ✅ | Target repository URL (e.g. `https://github.com/owner/repo`) |
263
- | `startingRef` | — | Branch/ref to start from (defaults to the repo default) |
264
- | `model` | — | Cursor model id (optional for cloud) |
230
+ | `startingRef` | — | Branch/ref to start from |
231
+ | `model` | — | Cursor model id |
265
232
  | `mode` | — | `"agent"` or `"plan"` |
266
- | `thinking` | — | Thinking level (e.g. `"high"`) |
267
- | `autoCreatePR` | — | Open a PR automatically when finished |
233
+ | `thinking` | — | Thinking level |
234
+ | `autoCreatePR` | — | Open a PR when finished |
268
235
  | `workOnCurrentBranch` | — | Operate on the current branch instead of a new one |
269
236
 
270
- ## Provider vs. delegation tools
237
+ ## Tool display
271
238
 
272
- This package ships two complementary ways to use Cursor inside opencode:
239
+ `toolDisplay` controls how Cursor's internal tool activity appears in opencode:
273
240
 
274
- - **Provider** (`cursor/*` models) — chat with a Cursor model directly, integrated into opencode's
275
- normal model/variant UX, with live model discovery, MCP forwarding, and session reuse. Cursor runs
276
- its own tools internally (surfaced per the [`toolDisplay`](#tool-display) option).
277
- - **Delegation tools** `cursor_delegate` hands a discrete subtask to a local Cursor agent as a
278
- permission-gated, auditable tool call (your primary opencode model stays in control); and
279
- `cursor_cloud_agent` launches a background agent on a remote repo that can run for minutes and
280
- optionally open a PR.
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.
281
246
 
282
- **When to use which.** Use the **provider** when you want Cursor to *be* a model you select and
283
- converse with, integrated into opencode's normal model/variant UX. Use **`cursor_delegate`** when
284
- you want your existing opencode model to stay in control and hand off discrete tasks as explicit,
285
- permission-gated tool calls. Use **`cursor_cloud_agent`** when you need background work on a remote
286
- repo with optional PR creation.
287
-
288
- ## Behavior & limitations
289
-
290
- > The provider path runs Cursor's own unsandboxed tools outside opencode's permission system — see
291
- > [Security](#security).
292
-
293
- This plugin runs Cursor as a **local agent** (`Agent.create({ local: { cwd } })`), so:
294
-
295
- - **Cursor executes its own tools** (read/write/edit/shell/grep/mcp/…) directly in your working
296
- directory. How that activity is shown is controlled by the [`toolDisplay`](#tool-display) option.
297
- Either way it is **not** routed through opencode's tool/permission system — Cursor runs the tools
298
- itself.
299
- - By default each turn creates a fresh local agent and sends the full conversation transcript, so
300
- context is always complete. Enable `session: true` to reuse Cursor's native per-agent memory
301
- across turns (see [Session reuse](#session-reuse-session)).
302
- - Token usage is reported from Cursor's `turn-ended` event; cost is shown as `0` because Cursor
303
- bills your account separately.
304
- - **Provider path is local.** The `cursor/*` models you chat with run as a **local** agent. Cursor's
305
- **cloud** runtime (background agents on a remote repo with optional PR creation) maps awkwardly
306
- onto a synchronous provider call, so it is exposed as the `cursor_cloud_agent` **tool** instead of
307
- the provider path — see [Delegation tools](#delegation-tools).
308
-
309
- ### Runtime: Bun and the Node sidecar
310
-
311
- opencode runs on [Bun](https://bun.sh). Bun's `node:http2` client is currently incompatible with the
312
- Cursor SDK's long-lived streaming RPC (it aborts the stream with `NGHTTP2_FRAME_SIZE_ERROR`), which
313
- makes Cursor's native tool calls execute but never report completion — they appear stuck or show
314
- "Tool execution aborted".
315
-
316
- To work around this transparently, when the plugin detects it is running under Bun and finds `node`
317
- on your `PATH`, it hosts the Cursor SDK agent in a short-lived **Node child process** (a "sidecar")
318
- and talks to it over stdio. Behavior is otherwise identical. Under Node the SDK runs in-process and
319
- no sidecar is spawned.
320
-
321
- - **Requirement:** a Node.js runtime on `PATH` when running under Bun — Node 22+ to match `engines`
322
- (the plugin checks that `node` is present, not its version). If Bun is detected but `node` is
323
- missing, the plugin logs a one-time warning and falls back to in-process (native Cursor tools will
324
- misbehave until Node is available).
325
- - **Override** with the `OPENCODE_CURSOR_SIDECAR` environment variable:
326
- - `OPENCODE_CURSOR_SIDECAR=1` — always use the sidecar (requires `node`).
327
- - `OPENCODE_CURSOR_SIDECAR=0` — never use the sidecar / silence the Bun warning.
247
+ To force the fallback:
328
248
 
329
- ## Tool display
330
-
331
- Cursor runs its own agent loop and executes its own tools. The `toolDisplay` option controls how
332
- that activity appears in opencode:
333
-
334
- - **`"blocks"` (default)** — tool activity is emitted as structured, **provider-executed**
335
- `tool-call`/`tool-result` parts so opencode renders proper, collapsible tool blocks with inputs
336
- and outputs. opencode skips execution for provider-executed calls (they're display-only), so
337
- Cursor's tools (`shell`, `mcp`, …) don't trigger an "unavailable tool" error. Requires a
338
- V3-native opencode host (1.16+).
339
-
340
- Where a Cursor tool has a natural opencode counterpart, it's surfaced under opencode's
341
- **registered** tool name so its native renderer is used instead of a generic block: `edit` →
342
- opencode's diff viewer (via `metadata.diff`), `shell` → `bash` console, `task` → the subagent
343
- card, web search (which Cursor runs as an MCP tool) → the `websearch` renderer, and
344
- `read`/`write`/`glob`/`grep`/`ls`/`updateTodos` → opencode's
345
- `read`/`write`/`glob`/`grep`/`list`/`todowrite` renderers. Cursor's arg shape is translated to
346
- opencode's (e.g. `path` → `filePath`); the call stays provider-executed, so it's display-only and
347
- never re-run on disk.
348
-
349
- Tools with no opencode counterpart still get cleaned up: `readLints` and `delete` render as
350
- formatted `cursor_*` blocks (a diagnostics list / a one-line confirmation) rather than raw JSON,
351
- and any MCP tool's `content` is flattened to readable text. Anything else — or a result with an
352
- unexpected shape — falls back to a prefixed `cursor_*` block with the raw payload.
353
- - **`"reasoning"` (fallback)** — each tool call is shown as a compact reasoning line
354
- (`[tool] write {"path":…}`; failures as `[tool] x failed`). Robust on every host: no tool-call
355
- parts cross into opencode, so there's no dependency on how the host treats provider-executed
356
- tools. Use this on older/non-V3 opencode hosts.
357
-
358
- The default needs no configuration. To force the reasoning fallback (e.g. on a pre-1.16 host):
359
-
360
- ```jsonc
361
- {
362
- "provider": {
363
- "cursor": {
364
- "options": { "toolDisplay": "reasoning" }
365
- }
366
- }
367
- }
249
+ ```json
250
+ { "provider": { "cursor": { "options": { "toolDisplay": "reasoning" } } } }
368
251
  ```
369
252
 
370
- > Why blocks by default: structured tool blocks are the nicer experience and have been verified
371
- > against opencode 1.16+. `"blocks"` depends on V3-native, provider-executed dynamic tool parts; if
372
- > your host predates that (or renders them poorly), set `"toolDisplay": "reasoning"` it requires
373
- > 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).
374
263
 
375
264
  ## Troubleshooting
376
265
 
377
- - **Native Cursor tools hang or show "Tool execution aborted" (`NGHTTP2_FRAME_SIZE_ERROR`).** This
378
- is the Bun `node:http2` incompatibility. Make sure **Node.js is installed and on your `PATH`** so
379
- the plugin can use the Node sidecar (see [Runtime](#runtime-bun-and-the-node-sidecar)); force it
380
- 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`.
381
268
  - **"Running under Bun without a usable Node sidecar" warning.** Install Node.js 22+, or set
382
269
  `OPENCODE_CURSOR_SIDECAR=0` to accept in-process behavior and silence the warning.
383
- - **"Could not locate the bindings file" / `node_sqlite3.node` not found.** `@cursor/sdk` depends on
384
- the native `sqlite3` addon, and opencode installs plugins with Bun, which skips sqlite3's install
385
- script so the prebuilt binary may be missing. The plugin detects this and self-heals on first SDK
386
- load by running sqlite3's own `prebuild-install -r napi` under your system Node (requires Node on
387
- `PATH`). If it can't (no Node, offline), it logs a one-line manual fix: `cd` into the printed
388
- sqlite3 directory and run `npx prebuild-install -r napi` (or `npm rebuild sqlite3`). Set
389
- `OPENCODE_CURSOR_DEBUG=1` to see the repair output.
390
- - **Plugin looks enabled but no `cursor` provider/models appear.** opencode caches a plugin by its
391
- install spec under `~/.cache/opencode/packages/`; a stale cache from an older version can persist.
392
- Pin an exact version (`@stablekernel/opencode-cursor@<version>`) or delete the cached dir and
393
- restart so opencode reinstalls.
394
- - **Only the four fallback models appear in the picker.** The live catalog loads after the first
395
- authenticated use — restart opencode once after logging in, or run `cursor_refresh_models` to
396
- force a refresh.
397
- - **Invalid or expired key.** The key is validated on first use (model discovery / first call), not
398
- at login, so that's where an error surfaces.
399
- - **Need more detail?** Set `OPENCODE_CURSOR_DEBUG=1` for provider and sidecar trace logging on
400
- 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`.
401
281
 
402
282
  ## Contributing
403
283
 
404
- Issues and pull requests are welcome. See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for development
405
- 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
406
286
  [issue tracker](https://github.com/stablekernel/opencode-cursor/issues); for security reports
407
- see **[SECURITY.md](./SECURITY.md)**.
287
+ see [SECURITY.md](./SECURITY.md).
408
288
 
409
289
  ## License
410
290