# cursor-oauth-opencode

OpenCode plugin that connects to Cursor's API, giving you access to Cursor
models inside OpenCode with full tool-calling support.

## Install

```sh
npx cursor-oauth-opencode setup --global
opencode auth login --provider cursor
```

For project-local setup:

```sh
npx cursor-oauth-opencode setup --project
opencode auth login --provider cursor
```

The setup command is idempotent. It adds the npm plugin entry and fallback
model registries for both `provider.cursor` and `provider.cursor-code` in
`opencode.json`, preserving existing user model overrides. Login stays on the
primary provider only (`opencode auth login --provider cursor`).

The package supports the OpenCode v1 hooks loader and the unreleased OpenCode
v2 plugin API from one install. Its `./server` entry is dual-shaped: v1 invokes
`server`, while v2 schema-decodes and invokes `setup`. OpenCode v2 normalizes
the legacy `plugin` / `provider` config keys used by setup, so the same config
works on both runtimes. v2 support remains provisional until upstream ships a
versioned release.

## Providers

The plugin registers two OpenCode providers that share one OAuth login, proxy,
and model catalog. Mode is selected in the model picker:

| Provider | Picker path | Behavior |
|----------|-------------|----------|
| **Cursor** | `cursor/<model>` | OpenCode-backed native tools plus residual MCP tools (code mode OFF) |
| **Cursor Code** | `cursor-code/<model>` | Single `code` sandbox tool (code mode ON) |

**Breaking default:** `cursor/*` is native-tools / code mode OFF. To keep the
previous sandbox-collapse behavior, pick `cursor-code/<model>`. There is no
auto-rewrite of a previously selected `cursor/*` model.

Fleet-wide kill switch (forces OFF even for `cursor-code/*`): set
`CURSOR_OPENCODE_CODE_MODE=0` or `provider.cursor.plugin.codeMode.enabled: false`.
That knob is read only under `provider.cursor.plugin` — a block under
`provider.cursor-code.plugin` has no effect.

OpenCode skill routing (lowercase `skill` tool) is on by default and constrained
to the system `<available_skills>` catalog. Disable with
`CURSOR_OPENCODE_SKILLS=0` or `provider.cursor.plugin.skills.enabled: false`.
Missing/empty/malformed catalogs fail closed (skill tool stripped), not free-string.

Normal `cursor/*` Runs derive one capability plan from the current OpenCode tool
catalog. Exact, schema-compatible built-ins use Cursor's fixed native protocol;
tools without a safe native equivalent remain ordinary MCP declarations:

| OpenCode tool | Cursor-native carriers |
|---------------|------------------------|
| `read` | Read, Ls, PI Read, PI Ls |
| `grep` | Grep, PI Grep |
| `bash` | Shell, streaming shell, mini-SWE bash, PI Bash |
| `glob` | Glob (`grepArgs` files-with-matches carrier) |
| `question` | AskQuestion |
| `task` | Task/subagent with OpenCode's dynamic agent catalog |
| `lsp` | ReadLints diagnostics capture facade (production gate closed); general LSP stays MCP-visible |
| `apply_patch` | Delete only; full `apply_patch` remains MCP-visible |

Promotion requires the exact lowercase built-in name, its trusted OpenCode
schema, and one unambiguous owner. Aliases, namespaced tools, incompatible
schemas, and custom/plugin tools stay MCP. A Run has zero MCP declarations only
when it has no residual tools. OpenCode still executes every mapped operation
and owns its permission checks.

Native Glob uses Cursor's live `globToolCall`, which executes through a
glob-shaped `grepArgs` / `grepResult` wire while OpenCode still owns the exact
`glob` operation. The protocol's `fetchToolCall` has a complete
`fetchArgs` / `fetchResult` client Exec pair, but grok-4.5-fast low does not list
or emit it even when explicitly allowed, so `webfetch` remains MCP-visible. The
separate approval-only `webFetchToolCall` also stays disabled because it cannot
carry OpenCode's result. Native Task is enabled only when the exact `task` description
contains OpenCode's parseable, sorted dynamic agent catalog; otherwise `task`
stays MCP-declared. The capture-only ReadLints facade requires a canonical `lsp`
schema with a `diagnostics` operation and maps zero-based ranges, LSP severity
1-4, file/permission/error variants, and exact Exec/tool identities to Cursor's
typed `diagnosticsResult`. Production promotion remains disabled: OpenCode
1.18.4 exposes no public diagnostics operation, and the pinned live Grok low
probe completed `readLintsToolCall` without sending the client
`diagnosticsArgs`/`diagnosticsResult` handoff. PI Edit stays disabled in
transparent mode: two low-effort `grok-4.5-fast` attempts produced MCP or no
ToolCall when it was allowed, and hiding raw `apply_patch` would remove add,
move, and multi-file operations that PI Edit cannot represent. Standard
`editToolCall` stays disabled because it has no client Exec/result lane. Default
`write` remains MCP because live capture produced only unpaired standard
`writeArgs`/`writeResult` traffic toward Cursor `agent-tools`, with no complete
standard or PI ToolCall identity and target sequence.

Cursor-native WebSearch is enabled only when OpenCode did not advertise its own
lowercase `websearch` tool. `cursor-code/*` is unchanged: its protocol allowlist
remains exactly the single MCP `code` tool.

## Reasoning variants

Reasoning-tier Cursor models are listed once in the picker; effort is chosen
in OpenCode's **Select variant** menu (not as separate model slugs):

| Picker model | Variants | Notes |
|--------------|----------|--------|
| `grok-4.6` / `grok-4.6-fast` | `low` / `medium` / `high` / `xhigh` | Cursor default is `high`; Fast is a separate picker family (2× price). Wire slugs are `cursor-grok-4.6-{effort}[-fast]` |
| `grok-4.5-fast` | `low` / `medium` / `high` | Cursor slug suffixes are shifted (`high` → `…-xhigh`); non-fast `grok-4.5` is omitted |
| `claude-4.6-opus`, `gpt-5.4`, `glm-5.2`, … | only levels Cursor exposes | e.g. opus → `high`, gpt-5.4 → `medium` |
| `composer-2.5` / `composer-2.5-fast` | `default` only | Non-effort axis; stay as separate models |

Legacy full slugs (`grok-4.5-fast-high`, …) still resolve as aliases. After
upgrade, re-run setup so `opencode.json` seeds the collapsed base ids.

## Manual config

If you do not want to use the setup command, add this to
`~/.config/opencode/opencode.json`:

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    "cursor-oauth-opencode@latest"
  ],
  "provider": {
    "cursor": {
      "name": "Cursor",
      "npm": "@ai-sdk/openai-compatible",
      "api": "http://127.0.0.1:65535/v1",
      "models": {
        "composer-2.5-fast": {
          "name": "Composer 2.5 Fast",
          "reasoning": true,
          "temperature": true,
          "attachment": false,
          "tool_call": true,
          "limit": {
            "context": 200000,
            "output": 64000
          }
        }
      }
    },
    "cursor-code": {
      "name": "Cursor Code",
      "npm": "@ai-sdk/openai-compatible",
      "api": "http://127.0.0.1:65535/v1/code",
      "models": {
        "composer-2.5-fast": {
          "name": "Composer 2.5 Fast",
          "reasoning": true,
          "temperature": true,
          "attachment": false,
          "tool_call": true,
          "limit": {
            "context": 200000,
            "output": 64000
          }
        }
      }
    }
  }
}
```

The fallback API URLs are placeholders so OpenCode can list the providers
before login. After auth, the plugin starts a local proxy and replaces Cursor
models with live Cursor model discovery. `cursor-code` must keep the `/v1/code`
path suffix (merge-safety if a request falls back to `provider.api`).

OpenCode v2's config normalizer accepts this v1-shaped `plugin` / `provider`
document and projects it into `plugins` / `providers`. If a v2-native
`plugins` entry already registers this package, setup recognizes its string or
`{ "package": "cursor-oauth-opencode" }` form and does not add a duplicate.

## Authenticate

```sh
opencode auth login --provider cursor
```

This opens Cursor OAuth in the browser. OpenCode v1 stores tokens in
`~/.local/share/opencode/auth.json`; v2 stores them as the `cursor` integration
credential. Both refresh automatically, and both providers share that one
Cursor login.

When refreshing a published plugin install, remove only
`~/.cache/opencode/packages/cursor-oauth-opencode@latest`. Do not delete
`~/.local/share/opencode/auth.json`; plugin cache cleanup does not require a new
OAuth login.

## Use

Start OpenCode and select a model under **Cursor** (native tools) or
**Cursor Code** (local `code` tool). The plugin starts a local
OpenAI-compatible proxy on demand and routes requests through Cursor's gRPC API.

## How it works

1. OAuth — browser-based login to Cursor via PKCE (`cursor` provider only).
2. Model discovery — queries Cursor's gRPC API for all available models.
3. Local proxy — translates chat completions into Cursor's protobuf/HTTP/2
   Connect protocol. Path selects mode: `/v1/*` (native) vs `/v1/code/*`
   (sandbox).
4. Code mode (`cursor-code`) — advertises **only** the MCP `code` tool to the
   Cursor model (allowed-tools hard-forced to `mcpToolCall`). The model writes
   one async JavaScript program as `code({script,files?})`; the proxy runs it
   in the in-proxy Node child sandbox (`code-sandbox.mjs`) and drives NDJSON
   client-tool waves (`await tools.X(args)` / `callTool`). Native
   read/list/grep/mutations are rejected with coaching that points back at
   `code`. The streaming tool-call accumulator is inert in this mode so the
   outer `code` name is never validated against the inner client catalog.
   `cursor/*` uses the request-aware native mapping above and declares only
   residual tools through MCP.
5. Request continuity — every plugin window and OpenCode session carries an
   immutable proxy scope. Parked tool calls route by session plus pending call
   ids. A **valid** Cursor checkpoint is authoritative when workspace identity
   and client-history prefix match; missing, corrupt, incomplete,
   cross-workspace, or history-diverged checkpoints append one model-visible
   recovery marker and rebuild from the client ledger/history instead of
   borrowing another workspace's state.

**Code mode kill switch:** `provider.cursor.plugin.codeMode.enabled` (default
`true`). Set `false` or `CURSOR_OPENCODE_CODE_MODE=0` to force normal mode even
on `/v1/code`.

**Rollback:** disable code mode with `provider.cursor.plugin.codeMode.enabled:
false` or `CURSOR_OPENCODE_CODE_MODE=0` (normal Cursor tools only). Do not
change Cursor headers, native tool allowlists, protobufs, or normal provider
behavior as part of rollback.

HTTP/2 transport runs through a Node child process (`h2-daemon.mjs` / legacy
`h2-bridge.mjs`) because Bun's `node:http2` support is not reliable against
Cursor's API.

### Protocol version and tool policy

These knobs live under `provider.cursor.plugin` (env overrides last). They
describe how this plugin talks to Cursor's private Agent Run protocol — that
surface is **not** a stable public API and can change without notice.

| Knob | Default | Env | Behavior |
|------|---------|-----|----------|
| *(client version)* | manifest pin `cli-2026.07.20-8cc9c0b` | `CURSOR_OPENCODE_CLIENT_VERSION` | Advertised `x-cursor-client-version` is the checked-in protocol manifest pin, not "newest installed cursor-agent". The env value is an explicit **development override** only. |
| `requestContext.inline` | `true` | `CURSOR_OPENCODE_INLINE_REQUEST_CONTEXT` | When on, the initial action and checkpoint `ResumeAction` carry the same immutable RequestContext snapshot (also used for refresh replies). Set `false` / `0` to omit both action carriers and roll back to refresh-only context. |
| `nativePolicy` | `transparent` | `CURSOR_OPENCODE_NATIVE_POLICY` | `transparent`: exact compatible built-ins use request-bound native carriers and only residual tools are MCP-declared. `dedupe-canary`: preserve the earlier read/list/grep dedupe behavior. `legacy`: keep the complete client catalog MCP-visible as an operator or circuit-breaker rollback. Code mode always declares only `code`. |
| `codeMode.enabled` | `true` | `CURSOR_OPENCODE_CODE_MODE` | `true` enables code mode on `/v1/code`; `false` / `0` forces normal mode even when path is `/v1/code`. |
| *(Run retries)* | `3` | `CURSOR_OPENCODE_RUN_RETRIES` | Maximum automatic retries for zero-byte transport failures and retryable Connect errors. Before output, the original action is replayed; after output, a safe latest checkpoint is resumed with `ResumeAction`. Set `0` to disable. |
| *(retry backoff)* | `500` ms | `CURSOR_OPENCODE_RUN_RETRY_BACKOFF_MS` | Delay before retrying `resource_exhausted` / `unavailable` Connect failures. |

**Declaration vs execution catalogs:** one immutable plan owns the model-visible
residual MCP catalog, complete internal execution catalog, exact native
bindings, RequestContext instructions, and allowed-tools header for a physical
Run. Refresh, retry, and checkpoint resume reuse that snapshot.

**Filesystem ownership:** Cursor-native `Read` and `Ls` delegate to the
advertised OpenCode `read` tool; Cursor-native `Grep` delegates to OpenCode
`grep`. The proxy translates their results back into Cursor's native result
messages but does not perform the operation. OpenCode therefore owns `read`,
`grep`, and `external_directory` permissions, including for valid absolute
paths outside the workspace.

**Shell cwd boundary:** existing model-supplied shell directories are
canonicalized and passed to OpenCode, including external directories and
symlink targets; OpenCode owns `external_directory` approval. Missing paths and
files still fail as typed tool errors before process spawn. Missing absolute
prefixes may use the workspace suffix rescue, and commands are never silently
relocated.

**Circuit breaker:** if `transparent` or `dedupe-canary` sees an impossible
unbound native exec, unhandled case, or duplicate/closed identity, new Runs in
that proxy context fall back to `legacy` declarations. In-flight execs still
finish with their matching native result/error type.

**Exec lifecycle / cancel:** each controlled exec gets a heartbeat while
active, a typed result or throw, and exactly one `streamClose`. Client abort
(Esc) sends protocol `CancelAction` and tears down the owned bridge, execs,
timers, and code sandbox.

**Tool ownership (normal `cursor/*`):** OpenCode remains the operation and
permission owner. Native dispatch uses the exact binding captured in the Run
plan, never alias search over residual MCP declarations. Unsupported native-only
options return case-correct typed errors rather than being dropped. Fetch, edit,
full patch, resources, skill, todo, plan/general LSP, image/background bridges,
and all custom tools stay MCP unless a separate contract-complete native adapter
is released. A capture-only ReadLints diagnostics adapter is present but its
production gate is closed; navigation, symbols, hover, and call hierarchy never
use it. The native-skill decision, live candidate matrix, and reconsideration
gate are documented in `docs/native-skill-carrier.md`.
Unsupported cloud subagents, agent stores, SCM/PR/VM/environment
mutation, MCP auth, custom modes, native background, image, and standard native
Write remain typed rejections/errors instead of being advertised optimistically.
Semantic code search remains the plugin tool `cursor_codebase_search`. It is
local-always with a bounded local result on every call; Cursor cloud is
optional semantic enrichment only if implementation is available after the
concurrent lane and, until then, local remains the sole serving path.
Cursor's `semSearchToolCall` carries args and
result inline on server interaction updates (`ToolCall` field 16; args/result
fields 1/2), but the pinned protocol has no matching `ExecServerMessage` request
or `ExecClientMessage` result case. It therefore cannot transparently return the
plugin-owned cloud/local result or preserve OpenCode permissions. Code mode
(`cursor-code/*`) still advertises exactly one MCP `code` tool and hard-forces
allowed-tools to `mcpToolCall` only.

**Codebase search status:** every result starts with these fields in order:
`Cursor index adapter session:`, `Cloud readiness:`, `Cloud freshness:`,
`Cloud query outcome:`, `Serving source:`, and `Scope:`. Readiness is durable
cloud state (`disabled`, `initializing`, `ready`, `degraded`, or `unavailable`),
while query outcome and serving source describe only the current call. A valid
cloud query with zero hits therefore remains `ready` even when local fallback
serves matches. Freshness becomes `stale-after-local-change` after a successful
bootstrap followed by a relevant watcher change; watcher changes do not claim
continuous cloud upload.

`targetDirectories` accepts existing workspace-relative directories. Cloud
retrieval remains workspace-wide top-K and decoded paths are post-filtered, so
scoped cloud calls report `post-filtered cloud top-K`. If every supplied target
is invalid, search does not broaden to the workspace. Local scan, cloud upload,
and returned cloud paths share the same exclusions: ignored build/dependency
directories, every `.env*`, `.npmrc`, `.pypirc`, credential/secret JSON names,
and private-key/certificate extensions (`.pem`, `.key`, `.p12`, `.pfx`, `.crt`,
`.cer`, `.der`). Local evidence is always available and never depends on cloud;
cloud failure never suppresses local results. When cloud enrichment is
available after the concurrent lane, merged results deduplicate by canonical
relative path and label source truthfully; until then, local remains the sole
serving path.

Cloud bootstrap is adapter-owned and non-blocking for provider load. Concurrent
callers join one flight, each RPC resolves the current OAuth token, successful
ensure outcomes are memoized, transient failures retry after a bounded cooldown,
and manager disposal aborts owned RPCs and waits. The local lane runs
concurrently and does not block on cloud.

**Search prompt isolation and compaction:** codebase search is exposed only as
the explicit `cursor_codebase_search` tool. The plugin does not add search
guidance or search results to system prompts. After compaction, old Cursor
checkpoints are reused only when compacted client history still has the
checkpoint prefix and every system blob can be safely rebased to the current
system prompt and workspace identity; otherwise state is rebuilt from the
compacted summary and retained tail.
Same-Run transport retry still uses only a safe latest checkpoint with
`ResumeAction`; the plugin does not invoke Cursor pre-compact or summarization
messages.

**Tool bridges** live only under `provider.cursor.plugin.toolBridges`:

| Bridge | Config | Env |
|--------|--------|-----|
| Generate image | `toolBridges.generateImage.enabled` (+ `modelId`) | `CURSOR_OPENCODE_GENERATE_IMAGE`, `CURSOR_OPENCODE_GENERATE_IMAGE_MODEL` |
| Native Write | `toolBridges.nativeWrite.enabled` | `CURSOR_OPENCODE_NATIVE_WRITE` |
| Conversation search (default on) | `toolBridges.conversationSearch.enabled` | `CURSOR_OPENCODE_CONVERSATION_SEARCH` |
| Background shell | `toolBridges.backgroundShell.enabled` | `CURSOR_OPENCODE_BACKGROUND_SHELL` |

Conversation search is limited to OpenCode sessions in the current workspace;
it serves title hits immediately and builds a bounded local message index in
the background. It never searches another workspace or Cursor's cloud cache.

**Current capture status (release truth):** C-IMAGE is
`NO_IMAGE_CAPABLE_MODEL` / no pass fixture; C-MEDIA has a valid pass fixture
captured from installed OpenCode 1.18.4, but media projection remains
immutable-gated off pending a reviewed release decision; C-WRITE is
`NATIVE_WRITE_WIRE_INCOMPLETE`: standard `writeArgs`/`writeResult` pairs appeared,
but no matching standard or PI ToolCall start/completion identity appeared, and
the emitted writes targeted Cursor `agent-tools` paths outside the isolated
workspace with nonmatching bytes. Flags do **not** bypass
those gates — `cursor_generate_image` and native Write remain unregistered /
runtime-unavailable, and native media projection remains disabled, even if the
flags are set true.
C-SEM-SEARCH was attempted through both a direct AgentService Run and pinned
Cursor Agent `2026.07.20-8cc9c0b` with only `semSearchToolCall` allowed on
`grok-4.5-fast` low. The account usage gate stopped both attempts before any
ToolCall, so the fixture is `unsupported`, not a fabricated wire pass. Native
SemSearch remains disabled unless a future pinned descriptor and live capture
prove a client Exec/result handoff that can carry the exact plugin result.

**Opt-in MCP background pair** (`toolBridges.backgroundShell.enabled` /
`CURSOR_OPENCODE_BACKGROUND_SHELL=1`): registers `cursor_background_shell`
and `cursor_await`. Requires OpenCode `bash` permission (and
`external_directory` when the cwd is outside the worktree). Output is
`<project>/terminals/<taskId>.txt`. Defaults: 1 MiB output cap, 30 min
runtime (hard max 6 h), 8 jobs/session, await up to 120 s (default 30 s),
completed-job TTL 6 h. Await is same-session only; cancel/dispose tears
down the process group. Registry is process-local — OpenCode/plugin restart
loses in-flight jobs. Native Cursor background/`Await` stay disabled
regardless.

### Prompt shaping (caveman + reasoning containment)

On every outbound Cursor Run the proxy can:

- **Caveman-compress** tool-description prose and the OpenCode system prompt
  (`plugin.caveman.level` / `CURSOR_OPENCODE_CAVEMAN`, default `full`; system
  inherits via `plugin.caveman.system` / `CURSOR_OPENCODE_CAVEMAN_SYSTEM`).
- Append a verbatim **reasoning guard** for runaway-reasoning models
  (`claude-fable-5`, `claude-opus-4-8`; `CURSOR_OPENCODE_REASONING_GUARD=0`).
- Inject a coalesced **harness appendix** (interstitial text, overthink
  `<system-reminder>`, throttled continuity, single-shot `[reasoning tail]` /
  `[thinking exhaustion]` notes) for those same models.

Savings counters live under `CURSOR_OPENCODE_SAVINGS=1` (plugin log file).

## Architecture

```
OpenCode v1 ./server `server` --> hooks (`auth.loader`, `chat.params`)
OpenCode v2 ./server `setup`  --> domains (`integration`, `catalog`, `session`)
                                  |
                                  v
OpenCode  -->  /v1/chat/completions       (cursor = code OFF)
          -->  /v1/code/chat/completions  (cursor-code = code ON)
                                               |
                                    Bun.serve (proxy)
                                              |
                                    Node child (h2-daemon / h2-bridge)
                                              |
                                     HTTP/2 Connect stream
                                              |
                                     api2.cursor.sh gRPC
                                       /agent.v1.AgentService/Run
```

Both current loaders prefer one dual `./server` module; the root export remains
an older-v1 fallback. The shared runtime lives in `src/plugin-core.ts`;
`src/index.ts` is the thin v1 adapter and `src/v2-entry.ts` is the v2
Promise-domain adapter. v2 pins the
live proxy URL through `session.hook("model.request")`, so it does not use the
v1 `:65535` fetch rewrite. The current v2 plugin tool context exposes neither
the v1 permission prompt nor abort/directory fields, so v2 registers only the
read-only `cursor_codebase_search` plugin tool; permission-sensitive
background-shell and image bridges remain v1-only until upstream exposes an
equivalent contract. v2 currently uses the host process cwd as workspace
identity because its plugin context does not expose directory/worktree.

### Tool call flow (code mode, `cursor-code`)

```
1. Proxy advertises ONE code tool to the Cursor model (its description embeds
   OpenCode's real tool catalog)
2. Model emits code({script}); proxy runs the script in a Node child sandbox
3. Each await tools.X() wave -> proxy emits OpenAI tool_calls SSE, parks BOTH
   the Cursor bridge and the sandbox
4. OpenCode executes the tools, sends results in the follow-up request
5. Proxy feeds results to the parked sandbox; the next wave repeats step 3
6. Script returns -> its value becomes the code tool result; the Cursor bridge
   resumes and streams the model's final answer
```

With `cursor/*` (or the kill switch forcing OFF) the proxy passes each client
tool through directly: the model calls a tool via `mcpArgs`, the proxy emits
OpenAI `tool_calls`, pauses the H2 stream, and resumes with `mcpResult` on the
follow-up request.

OpenCode `todowrite` deliberately stays on that MCP path. Cursor's native
`updateTodosToolCall` and `readTodosToolCall` carry their arguments and results
inside interaction updates and mutate Cursor's checkpoint-owned todo state;
the current protocol exposes no matching client Exec request/result pair. The
plugin therefore never hides OpenCode `todowrite`, fabricates native results,
or maintains a second todo ledger. Re-run the fail-closed live ownership probe
after a pinned protocol upgrade:

```sh
bun scripts/probe-tool-bridges.ts --case native-todo --model grok-4.5-fast --effort low
```

The expected current-build outcome is a nonzero exit plus a sanitized
`native-todo-sequence.json` fixture documenting `CURSOR_OWNS_TODO_STATE_NO_CLIENT_HANDOFF`.

## Develop locally

```sh
bun install
bun run build
bun run test
bun run test:serial  # serial diagnostics
bun run test:smoke   # parallel smoke groups only
```

The default test gate runs quality fixtures (including protocol-snapshot /
`proto:check`) alongside a parallel repository-gate phase: header/surface parity
plus seven Bun smoke groups (five-way concurrency). Tests sharing module state
remain serial within a group. Run one smoke group with `bun run test:group <name>`;
names: `core`, `index-context`, `code-mode`, `streaming-proxy`,
`streaming-lifecycle`, `transport`, `utilities`.

## Logs

Plugin diagnostics never print to the terminal (they would paint over the
OpenCode TUI). They land in `~/.cache/cursor-oauth-opencode/plugin.log`.
Override the path with `CURSOR_OPENCODE_LOG_FILE`; set
`CURSOR_OPENCODE_LOG_STDERR=1` to also mirror lines to stderr when debugging
outside the TUI.

## Requirements

- [OpenCode](https://opencode.ai)
- [Bun](https://bun.sh)
- [Node.js](https://nodejs.org) >= 18 for the HTTP/2 bridge process
- Active [Cursor](https://cursor.com) subscription
