@stablekernel/opencode-cursor 0.1.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 ADDED
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ - `0.1.0-rc.1` — first pre-release of the 0.1.0 surface below, published to the
8
+ npm `next` dist-tag for validation ahead of the stable `0.1.0`.
9
+
10
+ ## [0.1.0] — unreleased
11
+
12
+ Initial public release. A complete opencode integration for Cursor built on the
13
+ official `@cursor/sdk`: a streaming chat provider, an auth/config/model plugin,
14
+ and a permission-gated delegation tool surface.
15
+
16
+ ### Provider
17
+
18
+ - **Cursor provider** backed by the official `@cursor/sdk` — drives a local
19
+ Cursor agent (`Agent.create` / `agent.send`) and translates its `onDelta`
20
+ callbacks into AI SDK `LanguageModelV3` stream parts (text, reasoning,
21
+ tool activity, usage). Implements both `doStream()` and `doGenerate()`.
22
+ - **Per-request controls** via `providerOptions.cursor` — `mode` (agent/plan),
23
+ `params`, and `thinking` level; works with opencode's model variant picker.
24
+ - **Model variants** auto-generated from `Cursor.models.list` parameters: a
25
+ `plan` variant plus one per reasoning level a model advertises.
26
+ - **Session reuse** (`session: true`) — keeps one Cursor agent per opencode
27
+ session via `Agent.resume()` across turns, with automatic fallback to a fresh
28
+ agent. A run wedged by a crashed/duplicate process is recovered by retrying
29
+ the send once with the SDK's `local.force` escape hatch.
30
+ - **`toolDisplay` provider option** (`"reasoning"` default | `"blocks"`):
31
+ - `"reasoning"` renders Cursor's internal tool activity (including the real
32
+ MCP tool name) as concise `[tool] …` reasoning lines. Always safe — tool
33
+ calls never cross opencode's tool-execution boundary.
34
+ - `"blocks"` emits structured, provider-executed **dynamic** `tool-call` /
35
+ `tool-result` parts so opencode renders native tool blocks. Names are
36
+ `cursor_`-prefixed and sanitized (`shell` → `cursor_shell`,
37
+ `serena/find_symbol` → `cursor_serena_find_symbol`) so they can't collide
38
+ with opencode-registered tools, and carry `providerExecuted: true` +
39
+ `dynamic: true` so ai v6's `parseToolCall` accepts them without
40
+ registered-tool validation. Tool-results use the V3-spec `result` +
41
+ `isError` fields. A tool call whose completion never arrives (run
42
+ errored/cancelled mid-tool) is closed with a synthetic error result so the
43
+ block never dangles as "Tool execution aborted", and a run that ends with
44
+ status `error` surfaces the failure instead of finishing silently.
45
+
46
+ ### Node sidecar (Bun compatibility)
47
+
48
+ - **Automatic Node sidecar** — opencode runs on Bun, whose `node:http2` client
49
+ is incompatible with the Cursor SDK's long-lived streaming RPC
50
+ (`NGHTTP2_FRAME_SIZE_ERROR`), causing native tool calls to execute but never
51
+ report completion. When Bun is detected and `node` is on `PATH`, the SDK
52
+ agent is hosted in a Node child process and driven over a JSON-lines stdio
53
+ protocol; the provider is otherwise unchanged. Under Node the SDK runs
54
+ in-process. Override with `OPENCODE_CURSOR_SIDECAR=1` (force on) or
55
+ `OPENCODE_CURSOR_SIDECAR=0` (force in-process / silence the Bun warning).
56
+
57
+ ### Plugin
58
+
59
+ - **opencode plugin** (`@stablekernel/opencode-cursor/plugin`): auth hook (API-key login;
60
+ the key is validated on first use rather than at login), config hook
61
+ (auto-injects `provider.cursor`),
62
+ `provider.models()` (live catalog via `Cursor.models.list`), and the
63
+ `cursor_refresh_models` tool. The auth loader warms a key-independent catalog
64
+ cache so the model picker is populated on first authed load (and restart)
65
+ rather than showing only the fallback snapshot.
66
+ - **MCP server forwarding** — opencode's configured `config.mcp` entries are
67
+ translated to Cursor `McpServerConfig` and passed to the local agent so it can
68
+ use the same servers (e.g. Serena). Opt out with `provider.cursor.options.forwardMcp`.
69
+ - **Model discovery** with a 24-hour cache (keyed by key fingerprint) and a
70
+ built-in fallback snapshot (composer-2.5, claude-opus-4-8, claude-sonnet-4-6,
71
+ gpt-5.5) for use without an API key.
72
+
73
+ ### Delegation tools
74
+
75
+ - **`cursor_cloud_agent`** — launch a Cursor cloud (background) agent on a
76
+ remote repo via `Agent.create({ cloud: { repos, autoCreatePR } })`; returns
77
+ the agent id, terminal status, result, and PR url. Progress is collected from
78
+ `run.onDidChangeStatus`, `onStep`, and `onDelta`.
79
+ - **`cursor_delegate`** — run a single local Cursor turn as a permission-gated,
80
+ auditable opencode tool call (reuses the provider's `acquireAgent` +
81
+ `streamAgentTurn` plumbing). Both tools honor opencode's `permission` config
82
+ via `ToolContext.ask` and are fail-closed when no permission gate is present.
83
+
84
+ ### Tooling
85
+
86
+ - **Provider debug tracing** — opt-in via `OPENCODE_CURSOR_DEBUG=1`.
87
+ - End-to-end CI: unit tests on two Node versions plus a full integration test
88
+ (opencode loads the plugin, lists models, optionally runs a live chat turn).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 justin-carper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # @stablekernel/opencode-cursor
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@stablekernel/opencode-cursor.svg)](https://www.npmjs.com/package/@stablekernel/opencode-cursor)
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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
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`).
9
+
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 reasoning).
16
+
17
+ This plugin registers Cursor as a **native opencode provider**: its models appear in
18
+ `opencode models` and the model picker, and you talk to a Cursor model *directly* — with live model
19
+ discovery, variants, MCP forwarding, and session reuse. For delegated or background workflows it
20
+ also ships two permission-gated tools (`cursor_delegate`, `cursor_cloud_agent`); see
21
+ [Provider vs. delegation tools](#provider-vs-delegation-tools).
22
+
23
+ > ⚠️ **Security.** When you chat with a `cursor/*` model, Cursor runs its own tools — including
24
+ > `shell`, `write`, `edit`, and `delete` — directly in your working directory, **outside opencode's
25
+ > permission system and unsandboxed by default**. Read [Security](#security) before you use it.
26
+
27
+ ## Requirements
28
+
29
+ - **opencode 1.16+** — the provider targets AI SDK `LanguageModelV3`.
30
+ - **Node.js 22+ on your `PATH`** — opencode runs on [Bun](https://bun.sh); the plugin spawns a
31
+ short-lived Node process to host the Cursor SDK (see
32
+ [Runtime: Bun and the Node sidecar](#runtime-bun-and-the-node-sidecar)).
33
+ - A **Cursor account and API key** (from the Cursor dashboard).
34
+
35
+ ## Security
36
+
37
+ > ⚠️ **The provider path is unsandboxed and not gated by opencode permissions.**
38
+ > When you chat with a `cursor/*` model, Cursor runs its **own** agent loop and executes its own
39
+ > tools — including `shell`, `write`, `edit`, and `delete` — directly in your working directory.
40
+ > These run **outside** opencode's `permission` system, and the sandbox is **off by default**, so
41
+ > your opencode `permission` rules (e.g. `edit: deny`, `bash: ask`) do **not** apply to them. If you
42
+ > need an approval boundary, either set `sandbox: true` in `provider.cursor.options` (runs Cursor's
43
+ > tools in Cursor's sandbox) or use the permission-gated **`cursor_delegate`** tool instead of the
44
+ > provider path. Only the `cursor_delegate` / `cursor_cloud_agent` tools are gated by opencode's
45
+ > `permission` config.
46
+
47
+ See [SECURITY.md](./SECURITY.md) for the full threat model and how to report a vulnerability.
48
+
49
+ ## How it works
50
+
51
+ opencode loads two things from this one package:
52
+
53
+ | opencode concept | What it loads | Export |
54
+ | --- | --- | --- |
55
+ | Plugin (`plugin` config) | auth + provider registration + dynamic model listing + a refresh tool | `@stablekernel/opencode-cursor/plugin` |
56
+ | Provider (`provider.cursor.npm`) | a Vercel AI SDK `LanguageModelV3` that drives a local Cursor agent | `@stablekernel/opencode-cursor` (`createCursor`) |
57
+
58
+ The plugin's `config` hook registers `provider.cursor` (pointing `npm` at this package) and seeds
59
+ it with discovered/fallback models. The `auth` hook stores your API key and feeds it to the
60
+ provider factory; the key is validated on first use (model discovery / the first call), not at
61
+ login. The `provider.models()` hook refreshes the catalog live once you're authenticated.
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ npm install @stablekernel/opencode-cursor
67
+ ```
68
+
69
+ Add the plugin to your `opencode.json` (project or global):
70
+
71
+ ```json
72
+ {
73
+ "$schema": "https://opencode.ai/config.json",
74
+ "plugin": ["@stablekernel/opencode-cursor/plugin"]
75
+ }
76
+ ```
77
+
78
+ You do **not** need to hand-write the `provider` block — the plugin injects it. If you prefer to
79
+ configure it explicitly, the equivalent is:
80
+
81
+ ```json
82
+ {
83
+ "provider": {
84
+ "cursor": {
85
+ "npm": "@stablekernel/opencode-cursor",
86
+ "name": "Cursor",
87
+ "options": { "apiKey": "{env:CURSOR_API_KEY}" }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Authenticate
94
+
95
+ Either run the interactive login:
96
+
97
+ ```bash
98
+ opencode auth login # choose "Cursor", paste your key from the Cursor dashboard
99
+ ```
100
+
101
+ …or set the environment variable the Cursor SDK reads:
102
+
103
+ ```bash
104
+ export CURSOR_API_KEY="key_..."
105
+ ```
106
+
107
+ The key is never logged or written to disk by this plugin. It is validated on first use (model
108
+ discovery and the first call surface an invalid key clearly), not at login time.
109
+
110
+ ## Use
111
+
112
+ - `opencode models` (or the in-app model picker) lists your Cursor models as `cursor/<id>`.
113
+ - Pick a Cursor model and chat. The Cursor **local** agent runs in your project working directory.
114
+ - Run the `cursor_refresh_models` tool to force a live catalog refresh (bypasses the 24h cache).
115
+
116
+ This plugin also registers two **delegation tools** that complement the provider (see
117
+ [Delegation tools](#delegation-tools)):
118
+
119
+ - `cursor_delegate` — hand a discrete subtask to a local Cursor agent as a permission-gated,
120
+ auditable opencode tool call (your primary model stays in control).
121
+ - `cursor_cloud_agent` — launch a Cursor **cloud** (background) agent on a remote repo that can run
122
+ for minutes and optionally open a PR.
123
+
124
+ ## Configuration
125
+
126
+ | Option (`provider.cursor.options`) | Default | Meaning |
127
+ | --- | --- | --- |
128
+ | `apiKey` | `CURSOR_API_KEY` | Cursor API key |
129
+ | `cwd` | `process.cwd()` | Directory the local agent operates in |
130
+ | `mode` | `"agent"` | Default Cursor conversation mode (`"agent"` or `"plan"`) |
131
+ | `params` | — | Default Cursor model params, `{ <id>: value }` (e.g. `{ thinking: "high" }`) |
132
+ | `settingSources` | — | Cursor settings layers to load from disk: `["project","user","all",...]` — pulls in your Cursor **skills**, rules, and `.cursor/mcp.json` |
133
+ | `sandbox` | — | Run the agent's tools inside Cursor's sandbox (`true`/`false`) |
134
+ | `agents` | — | Cursor subagent definitions (`{ <name>: { description, prompt, model?, mcpServers? } }`) |
135
+ | `session` | `false` | Reuse one Cursor agent per opencode session (resume across turns; see below) |
136
+ | `forwardMcp` | `true` | Forward opencode's configured MCP servers to the Cursor agent |
137
+ | `mcpServers` | — | Extra MCP servers (Cursor `McpServerConfig` shape); merged with forwarded ones |
138
+ | `toolDisplay` | `"reasoning"` | How Cursor's internal tool activity is shown: `"reasoning"` (compact lines, works everywhere) or `"blocks"` (structured provider-executed tool blocks; opt-in, see [Tool display](#tool-display)) |
139
+
140
+ ### Session reuse (`session`)
141
+
142
+ By default each opencode turn spins up a **fresh** Cursor agent and re-sends the full conversation
143
+ transcript — robust, and correct even for opencode's non-chat calls (e.g. title generation). Set
144
+ `session: true` to instead keep **one Cursor agent per opencode session**: the provider names the
145
+ agent after the session, `Agent.resume()`s it on later turns, and sends only the new message so
146
+ Cursor uses its native conversation memory and checkpoints (the agent is visible in Cursor's
147
+ dashboard). The opencode session id reaches the provider via the plugin's `chat.params` hook
148
+ (`providerOptions.cursor.sessionID`); a failed resume falls back to a fresh turn automatically.
149
+
150
+ ### Per-request controls (`mode`, thinking level)
151
+
152
+ opencode delivers per-request, provider-specific settings to the model under
153
+ `providerOptions.cursor`. This plugin reads:
154
+
155
+ - `mode` → `"agent"` | `"plan"`
156
+ - `params` → `{ <paramId>: value }` mapped to Cursor `ModelSelection.params`
157
+ - `thinking` → convenience, mapped to the `thinking` param
158
+
159
+ These are most naturally driven by opencode's **model variant picker**: the plugin auto-generates a
160
+ `plan` variant plus one variant per reasoning/thinking value the model advertises
161
+ (`Cursor.models.list()` parameters). Selecting a variant sends its settings through
162
+ `providerOptions.cursor`. You can also set them statically per model:
163
+
164
+ ```json
165
+ { "provider": { "cursor": { "models": {
166
+ "composer-2.5": { "options": { "params": { "thinking": "high" } } }
167
+ } } } }
168
+ ```
169
+
170
+ | Environment variable | Default | Meaning |
171
+ | --- | --- | --- |
172
+ | `CURSOR_API_KEY` | — | API key fallback |
173
+ | `OPENCODE_CURSOR_MODEL_CACHE_TTL_MS` | `86400000` | Model-list cache lifetime |
174
+
175
+ To disable MCP forwarding, set `provider.cursor.options.forwardMcp: false` in your opencode config.
176
+
177
+ ## MCP servers
178
+
179
+ The Cursor agent can use the **same MCP servers you've configured in opencode**. The plugin's
180
+ `config` hook reads opencode's `config.mcp`, translates each entry into the Cursor SDK's
181
+ `McpServerConfig` shape, and hands them to the agent via `Agent.create({ mcpServers })`:
182
+
183
+ | opencode `config.mcp` | → Cursor |
184
+ | --- | --- |
185
+ | `{ type: "local", command: [cmd, ...args], environment }` | `{ type: "stdio", command: cmd, args, env }` |
186
+ | `{ type: "remote", url, headers }` | `{ type: "http", url, headers }` |
187
+
188
+ So if your `opencode.json` defines Serena, your Cursor agent connects to that same Serena — MCP
189
+ servers are independent processes, so opencode and the agent each connect to them directly.
190
+ Disabled entries (`enabled: false`) are skipped. Turn this off with `forwardMcp: false`.
191
+
192
+ > Scope note: this forwards **MCP servers**. opencode's *loop-internal* features — its own skills
193
+ > and subagents — are not exposed to the Cursor agent (they run inside opencode's agent loop, which
194
+ > this provider bypasses). The Cursor agent's *own* skills/rules can be loaded with the
195
+ > `settingSources` option (e.g. `["project","user"]`), which reads your local Cursor configuration.
196
+
197
+ ## Delegation tools
198
+
199
+ Alongside the provider, the plugin registers two tools so it is a **superset** of both the
200
+ provider and delegated-tool designs. Both resolve the Cursor API key from your `opencode auth login`
201
+ session (or `CURSOR_API_KEY`) and are **permission-gated** via opencode's `permission` config — they
202
+ call `context.ask`, so a policy of `allow` runs silently, `ask` prompts, and `deny` blocks:
203
+
204
+ ```json
205
+ { "permission": { "cursor_delegate": "ask", "cursor_cloud_agent": "ask" } }
206
+ ```
207
+
208
+ ### `cursor_delegate` (local)
209
+
210
+ Run a single Cursor turn on a fresh (or explicitly resumed) **local** agent and return its result.
211
+ Use it when your primary opencode model should stay in control and hand off discrete work to Cursor
212
+ as an explicit, auditable tool call.
213
+
214
+ | Arg | Required | Meaning |
215
+ | --- | --- | --- |
216
+ | `prompt` | ✅ | The subtask to delegate |
217
+ | `model` | ✅ | Cursor model id to run on |
218
+ | `mode` | — | `"agent"` or `"plan"` |
219
+ | `thinking` | — | Thinking level (e.g. `"high"`) |
220
+ | `cwd` | — | Working directory (defaults to the session directory) |
221
+ | `sandbox` | — | Run the agent's tools in Cursor's sandbox |
222
+ | `agentId` | — | Resume a specific Cursor agent id instead of starting fresh |
223
+
224
+ ### `cursor_cloud_agent` (cloud / background)
225
+
226
+ Launch a Cursor **cloud** agent against a remote repository. It runs autonomously (potentially for
227
+ minutes) and can open a pull request — work that maps poorly onto the synchronous provider path, so
228
+ it is exposed as a tool. Returns the cloud agent id, terminal status, result text, and PR url.
229
+
230
+ | Arg | Required | Meaning |
231
+ | --- | --- | --- |
232
+ | `prompt` | ✅ | The task for the background agent |
233
+ | `repoUrl` | ✅ | Target repository URL (e.g. `https://github.com/owner/repo`) |
234
+ | `startingRef` | — | Branch/ref to start from (defaults to the repo default) |
235
+ | `model` | — | Cursor model id (optional for cloud) |
236
+ | `mode` | — | `"agent"` or `"plan"` |
237
+ | `thinking` | — | Thinking level (e.g. `"high"`) |
238
+ | `autoCreatePR` | — | Open a PR automatically when finished |
239
+ | `workOnCurrentBranch` | — | Operate on the current branch instead of a new one |
240
+
241
+ ## Provider vs. delegation tools
242
+
243
+ This package ships two complementary ways to use Cursor inside opencode:
244
+
245
+ - **Provider** (`cursor/*` models) — chat with a Cursor model directly, integrated into opencode's
246
+ normal model/variant UX, with live model discovery, MCP forwarding, and session reuse. Cursor runs
247
+ its own tools internally (surfaced per the [`toolDisplay`](#tool-display) option).
248
+ - **Delegation tools** — `cursor_delegate` hands a discrete subtask to a local Cursor agent as a
249
+ permission-gated, auditable tool call (your primary opencode model stays in control); and
250
+ `cursor_cloud_agent` launches a background agent on a remote repo that can run for minutes and
251
+ optionally open a PR.
252
+
253
+ **When to use which.** Use the **provider** when you want Cursor to *be* a model you select and
254
+ converse with, integrated into opencode's normal model/variant UX. Use **`cursor_delegate`** when
255
+ you want your existing opencode model to stay in control and hand off discrete tasks as explicit,
256
+ permission-gated tool calls. Use **`cursor_cloud_agent`** when you need background work on a remote
257
+ repo with optional PR creation.
258
+
259
+ ## Behavior & limitations
260
+
261
+ > The provider path runs Cursor's own unsandboxed tools outside opencode's permission system — see
262
+ > [Security](#security).
263
+
264
+ This plugin runs Cursor as a **local agent** (`Agent.create({ local: { cwd } })`), so:
265
+
266
+ - **Cursor executes its own tools** (read/write/edit/shell/grep/mcp/…) directly in your working
267
+ directory. How that activity is shown is controlled by the [`toolDisplay`](#tool-display) option.
268
+ Either way it is **not** routed through opencode's tool/permission system — Cursor runs the tools
269
+ itself.
270
+ - By default each turn creates a fresh local agent and sends the full conversation transcript, so
271
+ context is always complete. Enable `session: true` to reuse Cursor's native per-agent memory
272
+ across turns (see [Session reuse](#session-reuse-session)).
273
+ - Token usage is reported from Cursor's `turn-ended` event; cost is shown as `0` because Cursor
274
+ bills your account separately.
275
+ - **Provider path is local.** The `cursor/*` models you chat with run as a **local** agent. Cursor's
276
+ **cloud** runtime (background agents on a remote repo with optional PR creation) maps awkwardly
277
+ onto a synchronous provider call, so it is exposed as the `cursor_cloud_agent` **tool** instead of
278
+ the provider path — see [Delegation tools](#delegation-tools).
279
+
280
+ ### Runtime: Bun and the Node sidecar
281
+
282
+ opencode runs on [Bun](https://bun.sh). Bun's `node:http2` client is currently incompatible with the
283
+ Cursor SDK's long-lived streaming RPC (it aborts the stream with `NGHTTP2_FRAME_SIZE_ERROR`), which
284
+ makes Cursor's native tool calls execute but never report completion — they appear stuck or show
285
+ "Tool execution aborted".
286
+
287
+ To work around this transparently, when the plugin detects it is running under Bun and finds `node`
288
+ on your `PATH`, it hosts the Cursor SDK agent in a short-lived **Node child process** (a "sidecar")
289
+ and talks to it over stdio. Behavior is otherwise identical. Under Node the SDK runs in-process and
290
+ no sidecar is spawned.
291
+
292
+ - **Requirement:** a Node.js runtime on `PATH` when running under Bun — Node 22+ to match `engines`
293
+ (the plugin checks that `node` is present, not its version). If Bun is detected but `node` is
294
+ missing, the plugin logs a one-time warning and falls back to in-process (native Cursor tools will
295
+ misbehave until Node is available).
296
+ - **Override** with the `OPENCODE_CURSOR_SIDECAR` environment variable:
297
+ - `OPENCODE_CURSOR_SIDECAR=1` — always use the sidecar (requires `node`).
298
+ - `OPENCODE_CURSOR_SIDECAR=0` — never use the sidecar / silence the Bun warning.
299
+
300
+ ## Tool display
301
+
302
+ Cursor runs its own agent loop and executes its own tools. The `toolDisplay` option controls how
303
+ that activity appears in opencode:
304
+
305
+ - **`"reasoning"` (default)** — each tool call is shown as a compact reasoning line
306
+ (`[tool] write {"path":…}`; failures as `[tool] x failed`). Robust on every host: no tool-call
307
+ parts cross into opencode, so there's no dependency on how the host treats provider-executed
308
+ tools.
309
+ - **`"blocks"` (opt-in)** — tool activity is emitted as structured, **provider-executed**
310
+ `tool-call`/`tool-result` parts so opencode renders proper, collapsible tool blocks with inputs
311
+ and outputs. opencode skips execution for provider-executed calls (they're display-only), so
312
+ Cursor's tools (`shell`, `mcp`, …) don't trigger an "unavailable tool" error. This requires a
313
+ V3-native opencode host (1.16+).
314
+
315
+ Enable blocks mode in your opencode config:
316
+
317
+ ```jsonc
318
+ {
319
+ "provider": {
320
+ "cursor": {
321
+ "options": { "toolDisplay": "blocks" }
322
+ }
323
+ }
324
+ }
325
+ ```
326
+
327
+ > Why opt-in: `"blocks"` depends on V3-native, provider-executed dynamic tool parts and has been
328
+ > verified against opencode 1.16+. `"reasoning"` requires nothing from the host and remains the
329
+ > always-safe default. If `"blocks"` renders cleanly for you, it's the nicer experience.
330
+
331
+ ## Troubleshooting
332
+
333
+ - **Native Cursor tools hang or show "Tool execution aborted" (`NGHTTP2_FRAME_SIZE_ERROR`).** This
334
+ is the Bun `node:http2` incompatibility. Make sure **Node.js is installed and on your `PATH`** so
335
+ the plugin can use the Node sidecar (see [Runtime](#runtime-bun-and-the-node-sidecar)); force it
336
+ with `OPENCODE_CURSOR_SIDECAR=1`.
337
+ - **"Running under Bun without a usable Node sidecar" warning.** Install Node.js 22+, or set
338
+ `OPENCODE_CURSOR_SIDECAR=0` to accept in-process behavior and silence the warning.
339
+ - **Only the four fallback models appear in the picker.** The live catalog loads after the first
340
+ authenticated use — restart opencode once after logging in, or run `cursor_refresh_models` to
341
+ force a refresh.
342
+ - **Invalid or expired key.** The key is validated on first use (model discovery / first call), not
343
+ at login, so that's where an error surfaces.
344
+ - **Need more detail?** Set `OPENCODE_CURSOR_DEBUG=1` for provider and sidecar trace logging on
345
+ stderr.
346
+
347
+ ## Contributing
348
+
349
+ Issues and pull requests are welcome. See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for development
350
+ setup, the test/typecheck/build commands, and the release process. Please report bugs at the
351
+ [issue tracker](https://github.com/stablekernel/opencode-cursor/issues); for security reports
352
+ see **[SECURITY.md](./SECURITY.md)**.
353
+
354
+ ## License
355
+
356
+ MIT