Skip to content

Commit b24199c

Browse files
committed
feat: Cursor provider plugin for opencode
opencode provider + plugin backed by @cursor/sdk, plus permission-gated cursor_delegate / cursor_cloud_agent tools. Bun-safe via an automatic Node sidecar for the SDK's streaming RPC. Prepared as v0.1.0-rc.1.
0 parents  commit b24199c

53 files changed

Lines changed: 10133 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/dependabot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
open-pull-requests-limit: 10
8+
groups:
9+
dev-dependencies:
10+
dependency-type: "development"
11+
12+
- package-ecosystem: "github-actions"
13+
directory: "/"
14+
schedule:
15+
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
build:
13+
name: typecheck · test · build (node ${{ matrix.node-version }})
14+
runs-on: ubuntu-latest
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
node-version: ["22.x", "24.x"]
19+
steps:
20+
- uses: actions/checkout@v6
21+
22+
- name: Set up Node ${{ matrix.node-version }}
23+
uses: actions/setup-node@v6
24+
with:
25+
node-version: ${{ matrix.node-version }}
26+
cache: npm
27+
28+
- name: Install dependencies
29+
run: npm ci
30+
31+
- name: Typecheck
32+
run: npm run typecheck
33+
34+
- name: Test
35+
run: npm test
36+
37+
- name: Build
38+
run: npm run build
39+
40+
integration:
41+
name: e2e · opencode loads plugin & lists models
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v6
45+
46+
- name: Set up Node
47+
uses: actions/setup-node@v6
48+
with:
49+
node-version: "24.x"
50+
cache: npm
51+
52+
- name: Install dependencies
53+
run: npm ci
54+
55+
- name: Install opencode, load the plugin, and list Cursor models
56+
run: bash scripts/integration-test.sh
57+
env:
58+
# Optional: when set, the script additionally verifies live auth and
59+
# Cursor.models.list(). Without it, the fallback model path is tested.
60+
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}

.github/workflows/release.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v[0-9]+.[0-9]+.[0-9]+"
7+
- "v[0-9]+.[0-9]+.[0-9]+-*"
8+
9+
permissions:
10+
contents: write # create GitHub Release
11+
id-token: write # npm provenance attestation
12+
13+
jobs:
14+
release:
15+
name: Publish to npm + create GitHub Release
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v6
19+
20+
- name: Set up Node
21+
uses: actions/setup-node@v6
22+
with:
23+
node-version: "22.x"
24+
registry-url: "https://registry.npmjs.org"
25+
cache: npm
26+
27+
- name: Install dependencies
28+
run: npm ci
29+
30+
- name: Typecheck, test, and build
31+
run: npm run prepublishOnly
32+
33+
# Run the full integration test (no CURSOR_API_KEY → fallback path).
34+
# Skip with SKIP_INTEGRATION=true for patch releases that don't touch
35+
# the plugin/provider entry-points.
36+
- name: Integration smoke test
37+
if: env.SKIP_INTEGRATION != 'true'
38+
run: bash scripts/integration-test.sh
39+
env:
40+
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
41+
42+
# A tag with a pre-release suffix (e.g. v0.1.0-rc.1) publishes to the
43+
# `next` dist-tag and is flagged as a GitHub pre-release, so `npm install`
44+
# and the repo's "Latest" release keep pointing at the last stable.
45+
- name: Classify release from tag
46+
id: version
47+
run: |
48+
VERSION="${GITHUB_REF_NAME#v}"
49+
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
50+
if [[ "$VERSION" == *-* ]]; then
51+
echo "NPM_TAG=next" >> "$GITHUB_OUTPUT"
52+
echo "PRERELEASE=true" >> "$GITHUB_OUTPUT"
53+
else
54+
echo "NPM_TAG=latest" >> "$GITHUB_OUTPUT"
55+
echo "PRERELEASE=false" >> "$GITHUB_OUTPUT"
56+
fi
57+
58+
- name: Publish to npm
59+
run: npm publish --provenance --access public --tag "${{ steps.version.outputs.NPM_TAG }}"
60+
env:
61+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
62+
63+
- name: Create GitHub Release
64+
uses: softprops/action-gh-release@v3
65+
with:
66+
name: "v${{ steps.version.outputs.VERSION }}"
67+
generate_release_notes: true
68+
prerelease: ${{ steps.version.outputs.PRERELEASE }}
69+
make_latest: ${{ steps.version.outputs.PRERELEASE == 'false' }}

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
node_modules/
2+
dist/
3+
*.log
4+
.DS_Store
5+
coverage/
6+
7+
# Packaging artifacts
8+
*.tgz
9+
10+
# Local editor / agent tooling
11+
.serena/
12+
.opencode/
13+
.claude/
14+
15+
# Local-only dev config (never commit)
16+
opencode.json

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
registry=https://registry.npmjs.org/

CHANGELOG.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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] — 2026-06-10
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).

CONTRIBUTING.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Contributing
2+
3+
Thanks for your interest in improving `@stablekernel/opencode-cursor`! Issues and pull
4+
requests are welcome.
5+
6+
## Reporting issues
7+
8+
- **Bugs / features:** open an issue at
9+
<https://github.com/stablekernel/opencode-cursor/issues>.
10+
- **Security vulnerabilities:** do **not** file a public issue — see
11+
[SECURITY.md](./SECURITY.md).
12+
13+
When reporting a runtime bug, please include:
14+
15+
- your runtime (Bun vs. Node) and version, and your opencode version,
16+
- whether the Node sidecar is in use (Bun + `node` on `PATH`),
17+
- output from running with `OPENCODE_CURSOR_DEBUG=1`.
18+
19+
## Development setup
20+
21+
Requires **Node.js 22+**.
22+
23+
```bash
24+
npm install
25+
npm run typecheck
26+
npm test
27+
npm run build
28+
29+
# End-to-end: install the real opencode CLI, load this plugin, and assert
30+
# `opencode models` lists the Cursor provider (no API key required — uses the
31+
# fallback snapshot; set CURSOR_API_KEY to also verify live discovery).
32+
bash scripts/integration-test.sh
33+
```
34+
35+
CI runs unit tests + build on Node 22 and 24 plus the end-to-end check on every
36+
push. Built with `@cursor/sdk`, `@opencode-ai/plugin`, and `@ai-sdk/provider`.
37+
38+
## Pull requests
39+
40+
- Add or update tests for behavior changes (this repo uses
41+
[Vitest](https://vitest.dev); see `test/`).
42+
- Keep `npm run typecheck`, `npm test`, and `npm run build` green.
43+
- Update `CHANGELOG.md` under the appropriate version/`[Unreleased]` heading.
44+
45+
## Releasing (maintainers)
46+
47+
The `.github/workflows/release.yml` workflow publishes automatically when a
48+
version tag is pushed:
49+
50+
```bash
51+
# 1. Bump the version in package.json (patch / minor / major)
52+
npm version patch # or: minor, major, or e.g. --new-version 0.2.0
53+
54+
# 2. Push the commit and the generated tag together
55+
git push origin main --follow-tags
56+
```
57+
58+
The release job will:
59+
60+
1. Run `prepublishOnly` (typecheck → test → build) to gate the publish.
61+
2. Run the integration smoke test (uses the `CURSOR_API_KEY` secret if set).
62+
3. Publish to npm with [provenance attestation](https://docs.npmjs.com/generating-provenance-statements).
63+
4. Create a GitHub Release with auto-generated release notes.
64+
65+
**Required repository secrets** (Settings → Secrets → Actions):
66+
67+
| Secret | Purpose |
68+
| --- | --- |
69+
| `NPM_TOKEN` | npm automation token with `publish` access |
70+
| `CURSOR_API_KEY` | (optional) live-path integration test during release |
71+
72+
**Pre-publish checklist:** update `CHANGELOG.md`, confirm `version` in
73+
`package.json` matches the tag, and ensure the branch is merged to `main`.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

0 commit comments

Comments
 (0)