@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 +10 -0
- package/README.md +146 -317
- package/dist/plugin/index.js +34 -10
- package/dist/plugin/index.js.map +1 -1
- package/dist/provider/index.js +12 -4
- package/dist/provider/index.js.map +1 -1
- package/package.json +1 -1
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
|
[](https://github.com/stablekernel/opencode-cursor/actions/workflows/ci.yml)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
|
-
An [opencode](https://opencode.ai) plugin that adds
|
|
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
|
|
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
|
|
13
|
+
> permission system**. Read [Security](#security) before you use it.
|
|
27
14
|
|
|
28
15
|
## Requirements
|
|
29
16
|
|
|
30
|
-
- **opencode 1.
|
|
31
|
-
- **Node.js 22+ on your `PATH`** — opencode runs on [Bun](https://bun.sh); the plugin
|
|
32
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
77
|
-
- `--yes` / `-y` — non-interactive
|
|
33
|
+
- `--project` — write `./opencode.json` in the current directory instead.
|
|
34
|
+
- `--yes` / `-y` — non-interactive.
|
|
78
35
|
|
|
79
|
-
|
|
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
|
|
38
|
+
### Manual
|
|
84
39
|
|
|
85
40
|
```bash
|
|
86
41
|
npm install @stablekernel/opencode-cursor
|
|
87
42
|
```
|
|
88
43
|
|
|
89
|
-
Add
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
133
|
-
- Pick a
|
|
134
|
-
- Run the `cursor_refresh_models` tool to force a live catalog refresh
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
- `cursor_cloud_agent` — launch a Cursor
|
|
142
|
-
|
|
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
|
|
151
|
-
| `params` | — | Default
|
|
152
|
-
| `settingSources` | — | Cursor settings layers to load
|
|
153
|
-
| `sandbox` | — | Run the agent's tools
|
|
154
|
-
| `agents` | — | Cursor subagent definitions
|
|
155
|
-
| `session` | `"auto"` | Session reuse strategy
|
|
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
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
Fingerprint records persist to `~/.cache/opencode-cursor/session-pool.json`, so session reuse
|
|
154
|
+
survives opencode restarts.
|
|
204
155
|
|
|
205
|
-
|
|
206
|
-
`providerOptions.cursor`. This plugin reads:
|
|
156
|
+
### Per-request controls (`mode`, thinking level)
|
|
207
157
|
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
185
|
+
| `{ type: "local", command: [cmd, ...args], environment }` | `{ type: "stdio", command, args, env }` |
|
|
252
186
|
| `{ type: "remote", url, headers }` | `{ type: "http", url, headers }` |
|
|
253
|
-
|
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
>
|
|
272
|
-
>
|
|
273
|
-
>
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
|
214
|
+
| `model` | ✅ | Cursor model id |
|
|
297
215
|
| `mode` | — | `"agent"` or `"plan"` |
|
|
298
216
|
| `thinking` | — | Thinking level (e.g. `"high"`) |
|
|
299
|
-
| `cwd` | — | Working directory
|
|
300
|
-
| `sandbox` | — | Run
|
|
301
|
-
| `agentId` | — | Resume a specific Cursor agent
|
|
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
|
|
221
|
+
### `cursor_cloud_agent` (cloud)
|
|
304
222
|
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
228
|
+
| `prompt` | ✅ | The task |
|
|
312
229
|
| `repoUrl` | ✅ | Target repository URL (e.g. `https://github.com/owner/repo`) |
|
|
313
|
-
| `startingRef` | — | Branch/ref to start from
|
|
314
|
-
| `model` | — | Cursor model id
|
|
230
|
+
| `startingRef` | — | Branch/ref to start from |
|
|
231
|
+
| `model` | — | Cursor model id |
|
|
315
232
|
| `mode` | — | `"agent"` or `"plan"` |
|
|
316
|
-
| `thinking` | — | Thinking level
|
|
317
|
-
| `autoCreatePR` | — | Open a PR
|
|
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
|
-
##
|
|
321
|
-
|
|
322
|
-
This package ships two complementary ways to use Cursor inside opencode:
|
|
237
|
+
## Tool display
|
|
323
238
|
|
|
324
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
247
|
+
To force the fallback:
|
|
381
248
|
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
|
429
|
-
|
|
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`
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
- **
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
456
|
-
|
|
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
|
|
287
|
+
see [SECURITY.md](./SECURITY.md).
|
|
459
288
|
|
|
460
289
|
## License
|
|
461
290
|
|